From 667189e7812f806d82c59bd19723422b50568c3e Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Tue, 12 Nov 2024 08:57:22 +0100 Subject: [PATCH 01/38] Initial code for embedded web service in Rust --- pdns/recursordist/rec-main.cc | 3 + pdns/recursordist/rec_control.cc | 20 ++ pdns/recursordist/reczones.cc | 2 + pdns/recursordist/settings/cxxsupport.cc | 2 + .../recursordist/settings/rust-preamble-in.rs | 2 + pdns/recursordist/settings/rust/Cargo.lock | 295 +++++++++++++++++- pdns/recursordist/settings/rust/Cargo.toml | 6 + pdns/recursordist/settings/rust/Makefile.am | 5 +- pdns/recursordist/settings/rust/build.rs | 3 +- pdns/recursordist/settings/rust/src/bridge.hh | 13 + pdns/recursordist/settings/rust/src/web.rs | 234 ++++++++++++++ pdns/recursordist/test-syncres_cc.cc | 21 ++ pdns/recursordist/ws-recursor.cc | 76 ++++- 13 files changed, 675 insertions(+), 7 deletions(-) create mode 100644 pdns/recursordist/settings/rust/src/web.rs diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index 2c0f123617b8..b25481c64eb2 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -44,6 +44,7 @@ #include "rec-system-resolve.hh" #include "root-dnssec.hh" #include "ratelimitedlog.hh" +#include "settings/rust/web.rs.h" #ifdef NOD_ENABLED #include "nod.hh" @@ -3319,6 +3320,8 @@ int main(int argc, char** argv) g_packetCache = std::make_unique(g_maxPacketCacheEntries, ::arg().asNum("packetcache-shards")); } + extern void serveRustWeb(); + serveRustWeb(); ret = serviceMain(startupLog); } catch (const PDNSException& ae) { diff --git a/pdns/recursordist/rec_control.cc b/pdns/recursordist/rec_control.cc index 2197ca0ba0ee..3e1a935704fd 100644 --- a/pdns/recursordist/rec_control.cc +++ b/pdns/recursordist/rec_control.cc @@ -448,3 +448,23 @@ int main(int argc, char** argv) return 1; } } + +void pdns::rust::web::rec::serveStuff(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) +{ +} + +void pdns::rust::web::rec::prometheusMetrics(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) +{ +} + +void pdns::rust::web::rec::apiServerZonesGET(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) +{ +} + +void pdns::rust::web::rec::apiServerCacheFlush(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) +{ +} + +void pdns::rust::web::rec::apiServerZonesPOST(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) +{ +} diff --git a/pdns/recursordist/reczones.cc b/pdns/recursordist/reczones.cc index ff3fbeeabda3..f03ea750746a 100644 --- a/pdns/recursordist/reczones.cc +++ b/pdns/recursordist/reczones.cc @@ -217,6 +217,8 @@ string reloadZoneConfiguration(bool yaml) for (const auto& entry : oldAndNewDomains) { wipeCaches(entry, true, 0xffff); } + extern std::shared_ptr g_initialDomainMap; // XXX + g_initialDomainMap = newDomainMap; return "ok\n"; } catch (const std::exception& e) { diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/settings/cxxsupport.cc index 4c4fd13c1a33..28bb3423f809 100644 --- a/pdns/recursordist/settings/cxxsupport.cc +++ b/pdns/recursordist/settings/cxxsupport.cc @@ -38,6 +38,7 @@ #include "dnsrecords.hh" #include "base64.hh" #include "validate-recursor.hh" +#include "threadname.hh" ::rust::Vec<::rust::String> pdns::settings::rec::getStrings(const std::string& name) { @@ -1453,3 +1454,4 @@ bool pdns::rust::settings::rec::isValidHostname(::rust::Str str) return false; } } + diff --git a/pdns/recursordist/settings/rust-preamble-in.rs b/pdns/recursordist/settings/rust-preamble-in.rs index 64fef08e6782..ee611f1533ef 100644 --- a/pdns/recursordist/settings/rust-preamble-in.rs +++ b/pdns/recursordist/settings/rust-preamble-in.rs @@ -30,6 +30,8 @@ use helpers::*; mod bridge; use bridge::*; +mod web; // leaving this out causes link issues + // Suppresses "Deserialize unused" warning #[derive(Deserialize, Serialize)] struct UnusedStruct {} diff --git a/pdns/recursordist/settings/rust/Cargo.lock b/pdns/recursordist/settings/rust/Cargo.lock index 807b4931c5e9..6dc7ce9f4855 100644 --- a/pdns/recursordist/settings/rust/Cargo.lock +++ b/pdns/recursordist/settings/rust/Cargo.lock @@ -2,18 +2,54 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "anyhow" version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + [[package]] name = "cc" version = "1.1.18" @@ -23,6 +59,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -83,12 +125,152 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[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-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", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", +] + [[package]] name = "indexmap" version = "2.5.0" @@ -111,6 +293,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "libc" +version = "0.2.162" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" + [[package]] name = "libyml" version = "0.0.5" @@ -136,12 +324,60 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "proc-macro2" version = "1.0.86" @@ -160,6 +396,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "ryu" version = "1.0.18" @@ -212,12 +454,18 @@ name = "settings" version = "5.2.0" dependencies = [ "base64", + "bytes", "cxx", "cxx-build", + "form_urlencoded", + "http-body-util", + "hyper", + "hyper-util", "ipnet", "once_cell", "serde", "serde_yml", + "tokio", ] [[package]] @@ -226,6 +474,22 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "syn" version = "2.0.77" @@ -246,6 +510,20 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "tokio" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +dependencies = [ + "backtrace", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -264,13 +542,28 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", ] [[package]] diff --git a/pdns/recursordist/settings/rust/Cargo.toml b/pdns/recursordist/settings/rust/Cargo.toml index fed8e823ad40..6765cbb99c08 100644 --- a/pdns/recursordist/settings/rust/Cargo.toml +++ b/pdns/recursordist/settings/rust/Cargo.toml @@ -15,6 +15,12 @@ serde_yaml = { package = "serde_yml", version = "0.0.12" } ipnet = "2.8" once_cell = "1.18.0" base64 = "0.22" +hyper = { version = "1", features = ["server", "http1"]} +tokio = { version = "1" , features = ["rt", "net"]} +http-body-util = "0.1" +hyper-util = { version = "0.1", features = ["tokio"]} +bytes = "1.8" +form_urlencoded = "1.2" [build-dependencies] cxx-build = "1.0" diff --git a/pdns/recursordist/settings/rust/Makefile.am b/pdns/recursordist/settings/rust/Makefile.am index 5d0f63741cc2..ae99af21eb1b 100644 --- a/pdns/recursordist/settings/rust/Makefile.am +++ b/pdns/recursordist/settings/rust/Makefile.am @@ -10,11 +10,12 @@ EXTRA_DIST = \ src/helpers.rs # should actually end up in a target specific dir... -libsettings.a lib.rs.h: src/bridge.rs src/lib.rs src/helpers.rs Cargo.toml Cargo.lock build.rs +libsettings.a lib.rs.h: src/web.rs src/bridge.rs src/lib.rs src/helpers.rs Cargo.toml Cargo.lock build.rs SYSCONFDIR=$(sysconfdir) NODCACHEDIRNOD=$(localstatedir)/nod NODCACHEDIRUDR=$(localstatedir)/udr $(CARGO) build --release $(RUST_TARGET) --target-dir=$(builddir)/target --manifest-path ${srcdir}/Cargo.toml cp target/$(RUSTC_TARGET_ARCH)/release/libsettings.a libsettings.a cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/settings/src/lib.rs.h lib.rs.h + cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/settings/src/web.rs.h web.rs.h cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/rust/cxx.h cxx.h clean-local: - rm -rf libsettings.a src/lib.rs lib.rs.h cxx.h target + rm -rf libsettings.a src/lib.rs lib.rs.h web.rs.h cxx.h target diff --git a/pdns/recursordist/settings/rust/build.rs b/pdns/recursordist/settings/rust/build.rs index ddddf7f2928e..e0fdf17c9c53 100644 --- a/pdns/recursordist/settings/rust/build.rs +++ b/pdns/recursordist/settings/rust/build.rs @@ -1,5 +1,6 @@ fn main() { - cxx_build::bridge("src/lib.rs") + let sources = vec!["src/lib.rs", "src/web.rs"]; + cxx_build::bridges(sources) // .file("src/source.cc") Code callable from Rust is in ../cxxsupport.cc .flag_if_supported("-std=c++17") .flag("-Isrc") diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index 6a6fd2b41d77..eca7324d9fa3 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -27,4 +27,17 @@ namespace pdns::rust::settings::rec { uint16_t qTypeStringToCode(::rust::Str str); bool isValidHostname(::rust::Str str); +void setThreadName(::rust::Str str); +} + +namespace pdns::rust::web::rec +{ +struct KeyValue; +struct Request; +struct Response; +void serveStuff(const Request& rustRequest, Response& rustResponse); +void prometheusMetrics(const Request& rustRequest, Response& rustResponse); +void apiServerCacheFlush(const Request& rustRequest, Response& rustResponse); +void apiServerZonesGET(const Request& rustRequest,Response& rustResponse); +void apiServerZonesPOST(const Request& rustRequest, Response& rustResponse); } diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs new file mode 100644 index 000000000000..06dfa0de117b --- /dev/null +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -0,0 +1,234 @@ +use std::net::SocketAddr; + +use bytes::Bytes; +use http_body_util::{BodyExt, Full}; +use hyper::{body::Incoming as IncomingBody, header, Method, Request, Response, StatusCode}; +use hyper::server::conn::http1; +use hyper::service::service_fn; +use hyper_util::rt::TokioIo; +use tokio::net::TcpListener; +use tokio::runtime::Builder; +use tokio::task::JoinSet; + +use std::io::ErrorKind; +use std::str::FromStr; + +type GenericError = Box; +type MyResult = std::result::Result; +type BoxBody = http_body_util::combinators::BoxBody; + +static NOTFOUND: &[u8] = b"Not Found"; + +fn full>(chunk: T) -> BoxBody { + Full::new(chunk.into()) + .map_err(|never| match never {}) + .boxed() +} + +type Func = fn(&rustweb::Request, &mut rustweb::Response) -> Result<(), cxx::Exception>; + +fn api_wrapper(handler: Func, request: &rustweb::Request, response: &mut rustweb::Response, headers: &mut header::HeaderMap) +{ + response.status = StatusCode::OK.as_u16(); // 200; + // security headers + headers.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, header::HeaderValue::from_static("*")); + headers.insert(header::X_CONTENT_TYPE_OPTIONS, header::HeaderValue::from_static("nosniff")); + headers.insert(header::X_FRAME_OPTIONS, header::HeaderValue::from_static("deny")); + headers.insert(header::HeaderName::from_static("x-permitted-cross-domain-policies"), header::HeaderValue::from_static("none")); + headers.insert(header::X_XSS_PROTECTION, header::HeaderValue::from_static("1; mode=block")); + headers.insert(header::CONTENT_SECURITY_POLICY, header::HeaderValue::from_static("default-src 'self'; style-src 'self' 'unsafe-inline'")); + + println!("api_wrapper A0 Status {}", response.status); + match handler(request, response) { + Ok(_) => { + } + Err(_) => { + response.status = StatusCode::UNPROCESSABLE_ENTITY.as_u16(); // 422 + } + } + println!("api_wrapper A Status {}", response.status); +} + +async fn hello(rust_request: Request, urls: &Vec) -> MyResult> { + let mut rust_response = Response::builder(); + let mut vars: Vec = vec![]; + if let Some(query) = rust_request.uri().query() { + for (k, v) in form_urlencoded::parse(query.as_bytes()) { + if k == "_" { // jQuery cache buster + continue; + } + let kv = rustweb::KeyValue{key: k.to_string(), value: v.to_string()}; + vars.push(kv); + } + } + let mut request = rustweb::Request{body: vec!(), uri: rust_request.uri().to_string(), vars: vars}; + let mut response = rustweb::Response{status: 0, body: vec![], headers: vec![]}; + let headers = rust_response.headers_mut().expect("no headers?"); + match (rust_request.method(), rust_request.uri().path()) { + (&Method::GET, "/metrics") => { + rustweb::prometheusMetrics(&request, &mut response).unwrap(); + } + (&Method::PUT, "/api/v1/servers/localhost/cache/flush") => { + api_wrapper(rustweb::apiServerCacheFlush as Func, &request, &mut response, headers); + } + (&Method::GET, "/api/v1/servers/localhost/zones") => { + println!("hello Status {}", response.status); + api_wrapper(rustweb::apiServerZonesGET as Func, &request, &mut response, headers); + } + (&Method::POST, "/api/v1/servers/localhost/zones") => { + request.body = rust_request.collect().await?.to_bytes().to_vec(); + api_wrapper(rustweb::apiServerZonesPOST as Func, &request, &mut response, headers); + } + _ => { + println!("{}", rust_request.uri().path()); + println!("{}", urls.len()); + let mut path = rust_request.uri().path(); + if path == "/" { + path = "/index.html"; + } + let pos = urls.iter().position(|x| { + String::from("/") + x == path + }); + println!("Pos is {:?}", pos); + if let Err(_) = rustweb::serveStuff(&request, &mut response) { + // Return 404 not found response. + response.status = StatusCode::NOT_FOUND.as_u16(); + response.body = NOTFOUND.to_vec(); + } + } + } + println!("B Status {}", response.status); + let mut rust_response = rust_response + .status(StatusCode::from_u16(response.status).unwrap()) + .body(full(response.body))?; + for kv in response.headers { + rust_response.headers_mut().insert(header::HeaderName::from_bytes(kv.key.as_bytes()).unwrap(), header::HeaderValue::from_str(kv.value.as_str()).unwrap()); + } + Ok(rust_response) +} + +async fn serveweb_async(listener: TcpListener, urls: &'static Vec) -> MyResult<()> { + + //let request_counter = Arc::new(AtomicUsize::new(0)); + /* + let fut = http1::Builder::new() + .serve_connection(move || { + service_fn(move |req| hello(req)) +}); + */ + // We start a loop to continuously accept incoming connections + loop { + let (stream, _) = listener.accept().await?; + + // Use an adapter to access something implementing `tokio::io` traits as if they implement + // `hyper::rt` IO traits. + let io = TokioIo::new(stream); + let fut = http1::Builder::new() + .serve_connection(io, service_fn(move |req| { + hello(req, urls) + })); + + // Spawn a tokio task to serve multiple connections concurrently + tokio::task::spawn(async move { + // Finally, we bind the incoming connection to our `hello` service + if let Err(err) = /* http1::Builder::new() + // `service_fn` converts our function in a `Service` + .serve_connection(io, service_fn(|req| hello(req))) + */ + fut.await + { + eprintln!("Error serving connection: {:?}", err); + } + }); + } +} + +pub fn serveweb(addresses: &Vec, urls: &'static Vec) -> Result<(), std::io::Error> { + + let runtime = Builder::new_current_thread() + .worker_threads(1) + .thread_name("rec/web") + .enable_io() + .build()?; + + let mut set = JoinSet::new(); + + for addr_str in addresses { + + // Socket create and bind should happen here + //let addr = SocketAddr::from_str(addr_str); + let addr = match SocketAddr::from_str(addr_str) { + Ok(val) => val, + Err(err) => { + let msg = format!("`{}' is not a IP:port combination: {}", addr_str, err); + return Err(std::io::Error::new(ErrorKind::Other, msg)); + } + }; + + let listener = runtime.block_on(async { + TcpListener::bind(addr).await + }); + + match listener { + Ok(val) => { + println!("Listening on {}", addr); + set.spawn_on(serveweb_async(val, urls), runtime.handle()); + }, + Err(err) => { + let msg = format!("Unable to bind web socket: {}", err); + return Err(std::io::Error::new(ErrorKind::Other, msg)); + } + } + } + std::thread::Builder::new() + .name(String::from("rec/rustweb")) + .spawn(move || { + runtime.block_on(async { + while let Some(res) = set.join_next().await { + println!("{:?}", res); + } + }); + })?; + Ok(()) +} + +#[cxx::bridge(namespace = "pdns::rust::web::rec")] +/* + * Functions callable from C++ + */ +mod rustweb { + + extern "Rust" { + fn serveweb(addreses: &Vec, urls: &'static Vec) -> Result<()>; + } + + struct KeyValue + { + key: String, + value: String, + } + + struct Request + { + body: Vec, + uri: String, + vars: Vec, + } + + struct Response + { + status: u16, + body: Vec, + headers: Vec, + } + + unsafe extern "C++" { + include!("bridge.hh"); + fn serveStuff(request: &Request, response: &mut Response) -> Result<()>; + fn prometheusMetrics(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerCacheFlush(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerZonesGET(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerZonesPOST(requst: &Request, response: &mut Response) -> Result<()>; + } + +} diff --git a/pdns/recursordist/test-syncres_cc.cc b/pdns/recursordist/test-syncres_cc.cc index 5c594122ef30..a6447f230baa 100644 --- a/pdns/recursordist/test-syncres_cc.cc +++ b/pdns/recursordist/test-syncres_cc.cc @@ -590,3 +590,24 @@ LWResult::Result basicRecordsForQnameMinimization(LWResult* res, const DNSName& } return LWResult::Result::Timeout; } + +void pdns::rust::web::rec::serveStuff(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) +{ +} + +void pdns::rust::web::rec::prometheusMetrics(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) +{ +} + +void pdns::rust::web::rec::apiServerZonesGET(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) +{ +} + +void pdns::rust::web::rec::apiServerCacheFlush(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) +{ +} + +void pdns::rust::web::rec::apiServerZonesPOST(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) +{ +} + diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 8e548ecdc413..0d8f5137a3a6 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -25,6 +25,7 @@ #include "ws-recursor.hh" #include "json.hh" +#include #include #include "namespaces.hh" #include @@ -45,9 +46,23 @@ #include "tcpiohandler.hh" #include "rec-main.hh" #include "settings/cxxsettings.hh" // IWYU pragma: keep, needed by included generated file +#include "settings/rust/web.rs.h" using json11::Json; +static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse) +{ + if (cxxresp.status != 0) { + rustResponse.status = cxxresp.status; + } + rustResponse.body = ::rust::Vec<::rust::u8>(); + rustResponse.body.reserve(cxxresp.body.size()); + std::copy(cxxresp.body.cbegin(), cxxresp.body.cend(), std::back_inserter(rustResponse.body)); + for (const auto& header : cxxresp.headers) { + rustResponse.headers.emplace_back(pdns::rust::web::rec::KeyValue{header.first, header.second}); + } +} + void productServerStatisticsFetch(map& out) { auto stats = getAllStatsMap(StatComponent::API); @@ -358,8 +373,8 @@ static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) DNSName zonename = apiNameToDNSName(stringFromJson(document, "name")); - const auto& iter = SyncRes::t_sstorage.domainmap->find(zonename); - if (iter != SyncRes::t_sstorage.domainmap->cend()) { + const auto& iter = g_initialDomainMap->find(zonename); + if (iter != g_initialDomainMap->cend()) { throw ApiException("Zone already exists"); } @@ -369,10 +384,20 @@ static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) resp->status = 201; } +void pdns::rust::web::rec::apiServerZonesPOST(const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustResponse) +{ + HttpRequest req; + HttpResponse resp; + + req.body = std::string(reinterpret_cast(rustRequest.body.data()), rustRequest.body.size()); + apiServerZonesPOST(&req, &resp); + fromCxxToRust(resp, rustResponse); +} + static void apiServerZonesGET(HttpRequest* /* req */, HttpResponse* resp) { Json::array doc; - for (const auto& val : *SyncRes::t_sstorage.domainmap) { + for (const auto& val : *g_initialDomainMap) { const SyncRes::AuthDomain& zone = val.second; Json::array servers; for (const auto& server : zone.d_servers) { @@ -391,6 +416,13 @@ static void apiServerZonesGET(HttpRequest* /* req */, HttpResponse* resp) resp->setJsonBody(doc); } +void pdns::rust::web::rec::apiServerZonesGET(const pdns::rust::web::rec::Request& /* rustRequest */, pdns::rust::web::rec::Response& rustResponse) +{ + HttpResponse resp; + apiServerZonesGET(nullptr, &resp); + fromCxxToRust(resp, rustResponse); +} + static inline DNSName findZoneById(HttpRequest* req) { auto zonename = apiZoneIdToName(req->parameters["id"]); @@ -472,6 +504,7 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) resp->setJsonBody(doc); } + static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) { DNSName canon = apiNameToDNSName(req->getvars["domain"]); @@ -487,6 +520,18 @@ static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {"result", "Flushed cache."}}); } +void pdns::rust::web::rec::apiServerCacheFlush(const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustResponse) +{ + HttpRequest request; + for (const auto& [key, value] : rustRequest.vars) { + cerr << key << ' ' << value << endl; + request.getvars[std::string(key)] = std::string(value); + } + HttpResponse response; + apiServerCacheFlush(&request, &response); + fromCxxToRust(response, rustResponse); +} + static void apiServerRPZStats(HttpRequest* /* req */, HttpResponse* resp) { auto luaconf = g_luaconfs.getLocal(); @@ -568,6 +613,13 @@ static void prometheusMetrics(HttpRequest* /* req */, HttpResponse* resp) resp->status = 200; } +void pdns::rust::web::rec::prometheusMetrics(const pdns::rust::web::rec::Request& /* rustRequest */, pdns::rust::web::rec::Response& rustReponse) +{ + HttpResponse resp; + prometheusMetrics(nullptr, &resp); + fromCxxToRust(resp, rustReponse); +} + #include "htmlfiles.h" static void serveStuff(HttpRequest* req, HttpResponse* resp) @@ -608,6 +660,15 @@ static void serveStuff(HttpRequest* req, HttpResponse* resp) } } +void pdns::rust::web::rec::serveStuff(const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustReponse) +{ + HttpRequest request; + HttpResponse response; + request.url = std::string(rustRequest.uri); + serveStuff(&request, &response); + fromCxxToRust(response, rustReponse); +} + const std::map MetricDefinitionStorage::d_metrics = { #include "rec-prometheus-gen.h" }; @@ -951,3 +1012,12 @@ void AsyncWebServer::go() } server->asyncWaitForConnections(d_fdm, [this](const std::shared_ptr& socket) { serveConnection(socket); }); } + +void serveRustWeb() +{ + static ::rust::Vec<::rust::String> urls; + for (const auto& [url, _] : g_urlmap) { + urls.emplace_back(url); + } + pdns::rust::web::rec::serveweb({"127.0.0.1:3000", "[::1]:3000"}, urls); +} From 39db08489291ce1d08db5d81fc67ffbb70ebed8d Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Thu, 21 Nov 2024 16:53:34 +0100 Subject: [PATCH 02/38] reformat --- pdns/recursordist/settings/rust/src/bridge.rs | 35 ++-- pdns/recursordist/settings/rust/src/web.rs | 149 +++++++++++------- 2 files changed, 119 insertions(+), 65 deletions(-) diff --git a/pdns/recursordist/settings/rust/src/bridge.rs b/pdns/recursordist/settings/rust/src/bridge.rs index 2ca152d15de7..83fc607bee9e 100644 --- a/pdns/recursordist/settings/rust/src/bridge.rs +++ b/pdns/recursordist/settings/rust/src/bridge.rs @@ -111,10 +111,7 @@ pub fn validate_socket_address_or_name(field: &str, val: &String) -> Result<(), let sa = validate_socket_address(field, val); if sa.is_err() && !isValidHostname(val) { let parts: Vec<&str> = val.split(':').collect(); - if parts.len() != 2 - || !isValidHostname(parts[0]) - || !is_port_number(parts[1]) - { + if parts.len() != 2 || !isValidHostname(parts[0]) || !is_port_number(parts[1]) { let msg = format!( "{}: value `{}' is not an IP, IP:port, name or name:port combination", field, val @@ -181,7 +178,12 @@ pub fn validate_subnet(field: &str, val: &String) -> Result<(), ValidationError> Ok(()) } -fn validate_address_family(addrfield: &str, localfield: &str, vec: &[String], local_address: &String) -> Result<(), ValidationError> { +fn validate_address_family( + addrfield: &str, + localfield: &str, + vec: &[String], + local_address: &String, +) -> Result<(), ValidationError> { if vec.is_empty() { let msg = format!("{}: cannot be empty", addrfield); return Err(ValidationError { msg }); @@ -208,15 +210,17 @@ fn validate_address_family(addrfield: &str, localfield: &str, vec: &[String], lo if local.is_ipv4() != ip.is_ipv4() || local.is_ipv6() != ip.is_ipv6() { wrong = true; } - } - else { + } else { let sa = sa.unwrap(); if local.is_ipv4() != sa.is_ipv4() || local.is_ipv6() != sa.is_ipv6() { wrong = true; } } if wrong { - let msg = format!("{}: value `{}' and `{}' differ in address family", localfield, local_address, addr_str); + let msg = format!( + "{}: value `{}' and `{}' differ in address family", + localfield, local_address, addr_str + ); return Err(ValidationError { msg }); } } @@ -501,7 +505,12 @@ impl RPZ { } self.tsig.validate(&(field.to_owned() + ".tsig"))?; if !self.addresses.is_empty() { - validate_address_family(&(field.to_owned() + ".addresses"), &(field.to_owned() + ".localAddress"), &self.addresses, &self.localAddress)?; + validate_address_family( + &(field.to_owned() + ".addresses"), + &(field.to_owned() + ".localAddress"), + &self.addresses, + &self.localAddress, + )?; } Ok(()) } @@ -579,7 +588,12 @@ impl ZoneToCache { &self.sources, validate_socket_address, )?; - validate_address_family(&(field.to_string() + ".sources"), &(field.to_string() + ".localAddress"), &self.sources, &self.localAddress)?; + validate_address_family( + &(field.to_string() + ".sources"), + &(field.to_string() + ".localAddress"), + &self.sources, + &self.localAddress, + )?; } self.tsig.validate(&(field.to_owned() + ".tsig"))?; Ok(()) @@ -1213,4 +1227,3 @@ pub fn validate_recordcache( pub fn validate_snmp(_snmp: &recsettings::Snmp) -> Result<(), ValidationError> { Ok(()) } - diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 06dfa0de117b..368c848e937e 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -2,9 +2,9 @@ use std::net::SocketAddr; use bytes::Bytes; use http_body_util::{BodyExt, Full}; -use hyper::{body::Incoming as IncomingBody, header, Method, Request, Response, StatusCode}; use hyper::server::conn::http1; use hyper::service::service_fn; +use hyper::{body::Incoming as IncomingBody, header, Method, Request, Response, StatusCode}; use hyper_util::rt::TokioIo; use tokio::net::TcpListener; use tokio::runtime::Builder; @@ -27,68 +27,117 @@ fn full>(chunk: T) -> BoxBody { type Func = fn(&rustweb::Request, &mut rustweb::Response) -> Result<(), cxx::Exception>; -fn api_wrapper(handler: Func, request: &rustweb::Request, response: &mut rustweb::Response, headers: &mut header::HeaderMap) -{ +fn api_wrapper( + handler: Func, + request: &rustweb::Request, + response: &mut rustweb::Response, + headers: &mut header::HeaderMap, +) { response.status = StatusCode::OK.as_u16(); // 200; - // security headers - headers.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, header::HeaderValue::from_static("*")); - headers.insert(header::X_CONTENT_TYPE_OPTIONS, header::HeaderValue::from_static("nosniff")); - headers.insert(header::X_FRAME_OPTIONS, header::HeaderValue::from_static("deny")); - headers.insert(header::HeaderName::from_static("x-permitted-cross-domain-policies"), header::HeaderValue::from_static("none")); - headers.insert(header::X_XSS_PROTECTION, header::HeaderValue::from_static("1; mode=block")); - headers.insert(header::CONTENT_SECURITY_POLICY, header::HeaderValue::from_static("default-src 'self'; style-src 'self' 'unsafe-inline'")); + // security headers + headers.insert( + header::ACCESS_CONTROL_ALLOW_ORIGIN, + header::HeaderValue::from_static("*"), + ); + headers.insert( + header::X_CONTENT_TYPE_OPTIONS, + header::HeaderValue::from_static("nosniff"), + ); + headers.insert( + header::X_FRAME_OPTIONS, + header::HeaderValue::from_static("deny"), + ); + headers.insert( + header::HeaderName::from_static("x-permitted-cross-domain-policies"), + header::HeaderValue::from_static("none"), + ); + headers.insert( + header::X_XSS_PROTECTION, + header::HeaderValue::from_static("1; mode=block"), + ); + headers.insert( + header::CONTENT_SECURITY_POLICY, + header::HeaderValue::from_static("default-src 'self'; style-src 'self' 'unsafe-inline'"), + ); println!("api_wrapper A0 Status {}", response.status); match handler(request, response) { - Ok(_) => { - } - Err(_) => { + Ok(_) => {} + Err(_) => { response.status = StatusCode::UNPROCESSABLE_ENTITY.as_u16(); // 422 } } println!("api_wrapper A Status {}", response.status); } -async fn hello(rust_request: Request, urls: &Vec) -> MyResult> { +async fn hello( + rust_request: Request, + urls: &Vec, +) -> MyResult> { let mut rust_response = Response::builder(); let mut vars: Vec = vec![]; if let Some(query) = rust_request.uri().query() { for (k, v) in form_urlencoded::parse(query.as_bytes()) { - if k == "_" { // jQuery cache buster + if k == "_" { + // jQuery cache buster continue; } - let kv = rustweb::KeyValue{key: k.to_string(), value: v.to_string()}; + let kv = rustweb::KeyValue { + key: k.to_string(), + value: v.to_string(), + }; vars.push(kv); } } - let mut request = rustweb::Request{body: vec!(), uri: rust_request.uri().to_string(), vars: vars}; - let mut response = rustweb::Response{status: 0, body: vec![], headers: vec![]}; + let mut request = rustweb::Request { + body: vec![], + uri: rust_request.uri().to_string(), + vars: vars, + }; + let mut response = rustweb::Response { + status: 0, + body: vec![], + headers: vec![], + }; let headers = rust_response.headers_mut().expect("no headers?"); match (rust_request.method(), rust_request.uri().path()) { (&Method::GET, "/metrics") => { rustweb::prometheusMetrics(&request, &mut response).unwrap(); } (&Method::PUT, "/api/v1/servers/localhost/cache/flush") => { - api_wrapper(rustweb::apiServerCacheFlush as Func, &request, &mut response, headers); + api_wrapper( + rustweb::apiServerCacheFlush as Func, + &request, + &mut response, + headers, + ); } (&Method::GET, "/api/v1/servers/localhost/zones") => { println!("hello Status {}", response.status); - api_wrapper(rustweb::apiServerZonesGET as Func, &request, &mut response, headers); + api_wrapper( + rustweb::apiServerZonesGET as Func, + &request, + &mut response, + headers, + ); } (&Method::POST, "/api/v1/servers/localhost/zones") => { request.body = rust_request.collect().await?.to_bytes().to_vec(); - api_wrapper(rustweb::apiServerZonesPOST as Func, &request, &mut response, headers); + api_wrapper( + rustweb::apiServerZonesPOST as Func, + &request, + &mut response, + headers, + ); } _ => { println!("{}", rust_request.uri().path()); println!("{}", urls.len()); - let mut path = rust_request.uri().path(); + let mut path = rust_request.uri().path(); if path == "/" { path = "/index.html"; } - let pos = urls.iter().position(|x| { - String::from("/") + x == path - }); + let pos = urls.iter().position(|x| String::from("/") + x == path); println!("Pos is {:?}", pos); if let Err(_) = rustweb::serveStuff(&request, &mut response) { // Return 404 not found response. @@ -102,20 +151,22 @@ async fn hello(rust_request: Request, urls: &Vec) -> MyRes .status(StatusCode::from_u16(response.status).unwrap()) .body(full(response.body))?; for kv in response.headers { - rust_response.headers_mut().insert(header::HeaderName::from_bytes(kv.key.as_bytes()).unwrap(), header::HeaderValue::from_str(kv.value.as_str()).unwrap()); + rust_response.headers_mut().insert( + header::HeaderName::from_bytes(kv.key.as_bytes()).unwrap(), + header::HeaderValue::from_str(kv.value.as_str()).unwrap(), + ); } Ok(rust_response) } async fn serveweb_async(listener: TcpListener, urls: &'static Vec) -> MyResult<()> { - //let request_counter = Arc::new(AtomicUsize::new(0)); /* - let fut = http1::Builder::new() - .serve_connection(move || { - service_fn(move |req| hello(req)) -}); - */ + let fut = http1::Builder::new() + .serve_connection(move || { + service_fn(move |req| hello(req)) + }); + */ // We start a loop to continuously accept incoming connections loop { let (stream, _) = listener.accept().await?; @@ -123,18 +174,16 @@ async fn serveweb_async(listener: TcpListener, urls: &'static Vec) -> My // Use an adapter to access something implementing `tokio::io` traits as if they implement // `hyper::rt` IO traits. let io = TokioIo::new(stream); - let fut = http1::Builder::new() - .serve_connection(io, service_fn(move |req| { - hello(req, urls) - })); + let fut = + http1::Builder::new().serve_connection(io, service_fn(move |req| hello(req, urls))); // Spawn a tokio task to serve multiple connections concurrently tokio::task::spawn(async move { // Finally, we bind the incoming connection to our `hello` service if let Err(err) = /* http1::Builder::new() - // `service_fn` converts our function in a `Service` - .serve_connection(io, service_fn(|req| hello(req))) - */ + // `service_fn` converts our function in a `Service` + .serve_connection(io, service_fn(|req| hello(req))) + */ fut.await { eprintln!("Error serving connection: {:?}", err); @@ -144,7 +193,6 @@ async fn serveweb_async(listener: TcpListener, urls: &'static Vec) -> My } pub fn serveweb(addresses: &Vec, urls: &'static Vec) -> Result<(), std::io::Error> { - let runtime = Builder::new_current_thread() .worker_threads(1) .thread_name("rec/web") @@ -154,7 +202,6 @@ pub fn serveweb(addresses: &Vec, urls: &'static Vec) -> Result<( let mut set = JoinSet::new(); for addr_str in addresses { - // Socket create and bind should happen here //let addr = SocketAddr::from_str(addr_str); let addr = match SocketAddr::from_str(addr_str) { @@ -165,15 +212,13 @@ pub fn serveweb(addresses: &Vec, urls: &'static Vec) -> Result<( } }; - let listener = runtime.block_on(async { - TcpListener::bind(addr).await - }); + let listener = runtime.block_on(async { TcpListener::bind(addr).await }); match listener { Ok(val) => { println!("Listening on {}", addr); set.spawn_on(serveweb_async(val, urls), runtime.handle()); - }, + } Err(err) => { let msg = format!("Unable to bind web socket: {}", err); return Err(std::io::Error::new(ErrorKind::Other, msg)); @@ -187,8 +232,8 @@ pub fn serveweb(addresses: &Vec, urls: &'static Vec) -> Result<( while let Some(res) = set.join_next().await { println!("{:?}", res); } - }); - })?; + }); + })?; Ok(()) } @@ -202,21 +247,18 @@ mod rustweb { fn serveweb(addreses: &Vec, urls: &'static Vec) -> Result<()>; } - struct KeyValue - { + struct KeyValue { key: String, value: String, } - struct Request - { + struct Request { body: Vec, uri: String, vars: Vec, } - struct Response - { + struct Response { status: u16, body: Vec, headers: Vec, @@ -230,5 +272,4 @@ mod rustweb { fn apiServerZonesGET(request: &Request, response: &mut Response) -> Result<()>; fn apiServerZonesPOST(requst: &Request, response: &mut Response) -> Result<()>; } - } From fe10c6b1567a88cb628147cc674e3f9c7a7d6ad3 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Thu, 21 Nov 2024 17:12:45 +0100 Subject: [PATCH 03/38] Clippy cleanup --- pdns/recursordist/settings/rust/src/web.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 368c848e937e..2ac5f2b6d2f7 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -72,7 +72,7 @@ fn api_wrapper( async fn hello( rust_request: Request, - urls: &Vec, + urls: &[String], ) -> MyResult> { let mut rust_response = Response::builder(); let mut vars: Vec = vec![]; @@ -92,7 +92,7 @@ async fn hello( let mut request = rustweb::Request { body: vec![], uri: rust_request.uri().to_string(), - vars: vars, + vars, }; let mut response = rustweb::Response { status: 0, @@ -131,18 +131,19 @@ async fn hello( ); } _ => { - println!("{}", rust_request.uri().path()); - println!("{}", urls.len()); let mut path = rust_request.uri().path(); if path == "/" { path = "/index.html"; } let pos = urls.iter().position(|x| String::from("/") + x == path); - println!("Pos is {:?}", pos); - if let Err(_) = rustweb::serveStuff(&request, &mut response) { + if pos.is_none() { + println!("{} not found", path); + } + if rustweb::serveStuff(&request, &mut response).is_err() { // Return 404 not found response. response.status = StatusCode::NOT_FOUND.as_u16(); response.body = NOTFOUND.to_vec(); + println!("{} not found case 2", path); } } } @@ -159,7 +160,7 @@ async fn hello( Ok(rust_response) } -async fn serveweb_async(listener: TcpListener, urls: &'static Vec) -> MyResult<()> { +async fn serveweb_async(listener: TcpListener, urls: &'static [String]) -> MyResult<()> { //let request_counter = Arc::new(AtomicUsize::new(0)); /* let fut = http1::Builder::new() @@ -192,7 +193,7 @@ async fn serveweb_async(listener: TcpListener, urls: &'static Vec) -> My } } -pub fn serveweb(addresses: &Vec, urls: &'static Vec) -> Result<(), std::io::Error> { +pub fn serveweb(addresses: &Vec, urls: &'static [String]) -> Result<(), std::io::Error> { let runtime = Builder::new_current_thread() .worker_threads(1) .thread_name("rec/web") @@ -244,7 +245,7 @@ pub fn serveweb(addresses: &Vec, urls: &'static Vec) -> Result<( mod rustweb { extern "Rust" { - fn serveweb(addreses: &Vec, urls: &'static Vec) -> Result<()>; + fn serveweb(addreses: &Vec, urls: &'static [String]) -> Result<()>; } struct KeyValue { From 71e102dd547ed8717b6004c79c2214ae27f22fbc Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Fri, 22 Nov 2024 11:40:02 +0100 Subject: [PATCH 04/38] Basic functionality works --- pdns/recursordist/Makefile.am | 2 + pdns/recursordist/rec-web-stubs.hh | 15 +++ pdns/recursordist/rec_control.cc | 20 +--- pdns/recursordist/settings/Makefile.am | 2 +- pdns/recursordist/settings/rust/.gitignore | 1 + pdns/recursordist/settings/rust/Makefile.am | 3 +- pdns/recursordist/settings/rust/src/bridge.hh | 7 +- pdns/recursordist/settings/rust/src/web.rs | 39 +++++-- pdns/recursordist/test-syncres_cc.cc | 21 +--- pdns/recursordist/ws-recursor.cc | 106 ++++++++---------- 10 files changed, 106 insertions(+), 110 deletions(-) create mode 100644 pdns/recursordist/rec-web-stubs.hh diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 92dcd7583a79..5bf734313e72 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -328,6 +328,7 @@ testrunner_SOURCES = \ rec-system-resolve.hh rec-system-resolve.cc \ rec-taskqueue.cc rec-taskqueue.hh \ rec-tcounters.cc rec-tcounters.hh \ + rec-web-stubs.hh \ rec-xfrtracker.cc \ rec-zonetocache.cc rec-zonetocache.hh \ recpacketcache.cc recpacketcache.hh \ @@ -543,6 +544,7 @@ rec_control_SOURCES = \ rec-system-resolve.cc rec-system-resolve.hh \ rec_channel.cc rec_channel.hh \ rec_control.cc \ + rec-web-stubs.hh \ settings/cxxsupport.cc \ sillyrecords.cc \ sortlist.cc sortlist.hh \ diff --git a/pdns/recursordist/rec-web-stubs.hh b/pdns/recursordist/rec-web-stubs.hh new file mode 100644 index 000000000000..bf9e68ff0199 --- /dev/null +++ b/pdns/recursordist/rec-web-stubs.hh @@ -0,0 +1,15 @@ +namespace pdns::rust::web::rec { +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define WRAPPER(A) void A(const Request& /* unused */ , Response& /* unused */) { } + +WRAPPER(jsonstat) +WRAPPER(apiServerCacheFlush) +WRAPPER(apiServerDetail) +WRAPPER(apiServerZonesGET) +WRAPPER(apiServerZonesPOST) +WRAPPER(prometheusMetrics) +WRAPPER(serveStuff) +WRAPPER(apiServerStatistics) + +#undef WRAPPER +} diff --git a/pdns/recursordist/rec_control.cc b/pdns/recursordist/rec_control.cc index 3e1a935704fd..a7936cfba5d1 100644 --- a/pdns/recursordist/rec_control.cc +++ b/pdns/recursordist/rec_control.cc @@ -448,23 +448,5 @@ int main(int argc, char** argv) return 1; } } +#include "rec-web-stubs.hh" -void pdns::rust::web::rec::serveStuff(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) -{ -} - -void pdns::rust::web::rec::prometheusMetrics(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) -{ -} - -void pdns::rust::web::rec::apiServerZonesGET(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) -{ -} - -void pdns::rust::web::rec::apiServerCacheFlush(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) -{ -} - -void pdns::rust::web::rec::apiServerZonesPOST(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) -{ -} diff --git a/pdns/recursordist/settings/Makefile.am b/pdns/recursordist/settings/Makefile.am index 8d186d14aaed..7bfd21922a65 100644 --- a/pdns/recursordist/settings/Makefile.am +++ b/pdns/recursordist/settings/Makefile.am @@ -22,7 +22,7 @@ BUILT_SOURCES=cxxsettings-generated.cc rust/src/lib.rs # with an rust/src/lib.rs.h that does not contain e.g. field name or field type changes. # # Use patterns to avoid having two instances of generate run simultaneously, a well-known hack for GNU make -cxxsettings-generated%cc rust/src/lib%rs: table.py generate.py rust-preamble-in.rs rust-bridge-in.rs docs-old-preamble-in.rst docs-new-preamble-in.rst +cxxsettings-generated%cc rust/src/lib%rs rust/src/web%rs: table.py generate.py rust-preamble-in.rs rust-bridge-in.rs docs-old-preamble-in.rst docs-new-preamble-in.rst @if test "$(PYTHON)" = ":"; then echo "Settings table table.py has changed, python is needed to regenerate the related settings files but python was not found. Please install python and re-run configure"; exit 1; fi @if ! $(PYTHON) --version | grep -q "Python 3"; then echo $(PYTHON) should be at least version 3. Please install python 3 and re-run configure; exit 1; fi $(MAKE) -C rust clean diff --git a/pdns/recursordist/settings/rust/.gitignore b/pdns/recursordist/settings/rust/.gitignore index eacb110ccd06..d31bd391e756 100644 --- a/pdns/recursordist/settings/rust/.gitignore +++ b/pdns/recursordist/settings/rust/.gitignore @@ -3,5 +3,6 @@ /Makefile.in /cxx.h /lib.rs.h +/web.rs.h src/lib.rs .dir-locals.el diff --git a/pdns/recursordist/settings/rust/Makefile.am b/pdns/recursordist/settings/rust/Makefile.am index ae99af21eb1b..6299a9f51e02 100644 --- a/pdns/recursordist/settings/rust/Makefile.am +++ b/pdns/recursordist/settings/rust/Makefile.am @@ -7,7 +7,8 @@ EXTRA_DIST = \ Cargo.lock \ build.rs \ src/bridge.rs \ - src/helpers.rs + src/helpers.rs \ + src/web.rs # should actually end up in a target specific dir... libsettings.a lib.rs.h: src/web.rs src/bridge.rs src/lib.rs src/helpers.rs Cargo.toml Cargo.lock build.rs diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index eca7324d9fa3..4ab8f387f8e9 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -35,9 +35,12 @@ namespace pdns::rust::web::rec struct KeyValue; struct Request; struct Response; -void serveStuff(const Request& rustRequest, Response& rustResponse); -void prometheusMetrics(const Request& rustRequest, Response& rustResponse); void apiServerCacheFlush(const Request& rustRequest, Response& rustResponse); +void apiServerDetail(const Request& rustRequest, Response& rustResponse); +void apiServerStatistics(const Request& rustRequest, Response& rustResponse); void apiServerZonesGET(const Request& rustRequest,Response& rustResponse); void apiServerZonesPOST(const Request& rustRequest, Response& rustResponse); +void prometheusMetrics(const Request& rustRequest, Response& rustResponse); +void serveStuff(const Request& rustRequest, Response& rustResponse); +void jsonstat(const Request& rustRequest, Response& rustResponse); } diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 2ac5f2b6d2f7..db56e23a7fe0 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -60,14 +60,12 @@ fn api_wrapper( header::HeaderValue::from_static("default-src 'self'; style-src 'self' 'unsafe-inline'"), ); - println!("api_wrapper A0 Status {}", response.status); match handler(request, response) { Ok(_) => {} Err(_) => { response.status = StatusCode::UNPROCESSABLE_ENTITY.as_u16(); // 422 } } - println!("api_wrapper A Status {}", response.status); } async fn hello( @@ -101,8 +99,13 @@ async fn hello( }; let headers = rust_response.headers_mut().expect("no headers?"); match (rust_request.method(), rust_request.uri().path()) { - (&Method::GET, "/metrics") => { - rustweb::prometheusMetrics(&request, &mut response).unwrap(); + (&Method::GET, "/jsonstat") => { + api_wrapper( + rustweb::jsonstat as Func, + &request, + &mut response, + headers, + ); } (&Method::PUT, "/api/v1/servers/localhost/cache/flush") => { api_wrapper( @@ -112,8 +115,15 @@ async fn hello( headers, ); } + (&Method::GET, "/api/v1/servers/localhost/statistics") => { + api_wrapper( + rustweb::apiServerStatistics as Func, + &request, + &mut response, + headers, + ); + } (&Method::GET, "/api/v1/servers/localhost/zones") => { - println!("hello Status {}", response.status); api_wrapper( rustweb::apiServerZonesGET as Func, &request, @@ -130,6 +140,17 @@ async fn hello( headers, ); } + (&Method::GET, "/api/v1/servers/localhost") => { + api_wrapper( + rustweb::apiServerDetail as Func, + &request, + &mut response, + headers, + ); + } + (&Method::GET, "/metrics") => { + rustweb::prometheusMetrics(&request, &mut response).unwrap(); + } _ => { let mut path = rust_request.uri().path(); if path == "/" { @@ -147,7 +168,6 @@ async fn hello( } } } - println!("B Status {}", response.status); let mut rust_response = rust_response .status(StatusCode::from_u16(response.status).unwrap()) .body(full(response.body))?; @@ -267,10 +287,13 @@ mod rustweb { unsafe extern "C++" { include!("bridge.hh"); - fn serveStuff(request: &Request, response: &mut Response) -> Result<()>; - fn prometheusMetrics(request: &Request, response: &mut Response) -> Result<()>; fn apiServerCacheFlush(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerDetail(requst: &Request, response: &mut Response) -> Result<()>; + fn apiServerStatistics(requst: &Request, response: &mut Response) -> Result<()>; fn apiServerZonesGET(request: &Request, response: &mut Response) -> Result<()>; fn apiServerZonesPOST(requst: &Request, response: &mut Response) -> Result<()>; + fn jsonstat(request: &Request, response: &mut Response) -> Result<()>; + fn prometheusMetrics(request: &Request, response: &mut Response) -> Result<()>; + fn serveStuff(request: &Request, response: &mut Response) -> Result<()>; } } diff --git a/pdns/recursordist/test-syncres_cc.cc b/pdns/recursordist/test-syncres_cc.cc index a6447f230baa..a38c1569439b 100644 --- a/pdns/recursordist/test-syncres_cc.cc +++ b/pdns/recursordist/test-syncres_cc.cc @@ -591,23 +591,4 @@ LWResult::Result basicRecordsForQnameMinimization(LWResult* res, const DNSName& return LWResult::Result::Timeout; } -void pdns::rust::web::rec::serveStuff(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) -{ -} - -void pdns::rust::web::rec::prometheusMetrics(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) -{ -} - -void pdns::rust::web::rec::apiServerZonesGET(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) -{ -} - -void pdns::rust::web::rec::apiServerCacheFlush(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) -{ -} - -void pdns::rust::web::rec::apiServerZonesPOST(const pdns::rust::web::rec::Request& /* unused */, pdns::rust::web::rec::Response& /* unused */) -{ -} - +#include "rec-web-stubs.hh" diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 0d8f5137a3a6..afee7d182706 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -50,19 +50,6 @@ using json11::Json; -static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse) -{ - if (cxxresp.status != 0) { - rustResponse.status = cxxresp.status; - } - rustResponse.body = ::rust::Vec<::rust::u8>(); - rustResponse.body.reserve(cxxresp.body.size()); - std::copy(cxxresp.body.cbegin(), cxxresp.body.cend(), std::back_inserter(rustResponse.body)); - for (const auto& header : cxxresp.headers) { - rustResponse.headers.emplace_back(pdns::rust::web::rec::KeyValue{header.first, header.second}); - } -} - void productServerStatisticsFetch(map& out) { auto stats = getAllStatsMap(StatComponent::API); @@ -384,16 +371,6 @@ static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) resp->status = 201; } -void pdns::rust::web::rec::apiServerZonesPOST(const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustResponse) -{ - HttpRequest req; - HttpResponse resp; - - req.body = std::string(reinterpret_cast(rustRequest.body.data()), rustRequest.body.size()); - apiServerZonesPOST(&req, &resp); - fromCxxToRust(resp, rustResponse); -} - static void apiServerZonesGET(HttpRequest* /* req */, HttpResponse* resp) { Json::array doc; @@ -416,13 +393,6 @@ static void apiServerZonesGET(HttpRequest* /* req */, HttpResponse* resp) resp->setJsonBody(doc); } -void pdns::rust::web::rec::apiServerZonesGET(const pdns::rust::web::rec::Request& /* rustRequest */, pdns::rust::web::rec::Response& rustResponse) -{ - HttpResponse resp; - apiServerZonesGET(nullptr, &resp); - fromCxxToRust(resp, rustResponse); -} - static inline DNSName findZoneById(HttpRequest* req) { auto zonename = apiZoneIdToName(req->parameters["id"]); @@ -520,18 +490,6 @@ static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {"result", "Flushed cache."}}); } -void pdns::rust::web::rec::apiServerCacheFlush(const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustResponse) -{ - HttpRequest request; - for (const auto& [key, value] : rustRequest.vars) { - cerr << key << ' ' << value << endl; - request.getvars[std::string(key)] = std::string(value); - } - HttpResponse response; - apiServerCacheFlush(&request, &response); - fromCxxToRust(response, rustResponse); -} - static void apiServerRPZStats(HttpRequest* /* req */, HttpResponse* resp) { auto luaconf = g_luaconfs.getLocal(); @@ -613,13 +571,6 @@ static void prometheusMetrics(HttpRequest* /* req */, HttpResponse* resp) resp->status = 200; } -void pdns::rust::web::rec::prometheusMetrics(const pdns::rust::web::rec::Request& /* rustRequest */, pdns::rust::web::rec::Response& rustReponse) -{ - HttpResponse resp; - prometheusMetrics(nullptr, &resp); - fromCxxToRust(resp, rustReponse); -} - #include "htmlfiles.h" static void serveStuff(HttpRequest* req, HttpResponse* resp) @@ -660,15 +611,6 @@ static void serveStuff(HttpRequest* req, HttpResponse* resp) } } -void pdns::rust::web::rec::serveStuff(const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustReponse) -{ - HttpRequest request; - HttpResponse response; - request.url = std::string(rustRequest.uri); - serveStuff(&request, &response); - fromCxxToRust(response, rustReponse); -} - const std::map MetricDefinitionStorage::d_metrics = { #include "rec-prometheus-gen.h" }; @@ -1019,5 +961,51 @@ void serveRustWeb() for (const auto& [url, _] : g_urlmap) { urls.emplace_back(url); } - pdns::rust::web::rec::serveweb({"127.0.0.1:3000", "[::1]:3000"}, urls); + pdns::rust::web::rec::serveweb({"127.0.0.1:3000", "[::1]:3000"}, ::rust::Slice{urls.data(), urls.size()}); +} + +static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse) +{ + if (cxxresp.status != 0) { + rustResponse.status = cxxresp.status; + } + rustResponse.body = ::rust::Vec<::rust::u8>(); + rustResponse.body.reserve(cxxresp.body.size()); + std::copy(cxxresp.body.cbegin(), cxxresp.body.cend(), std::back_inserter(rustResponse.body)); + for (const auto& header : cxxresp.headers) { + rustResponse.headers.emplace_back(pdns::rust::web::rec::KeyValue{header.first, header.second}); + } +} + +static void rustWrapper(const std::function& func, const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustResponse) +{ + HttpRequest request; + HttpResponse response; + request.body = std::string(reinterpret_cast(rustRequest.body.data()), rustRequest.body.size()); + request.url = std::string(rustRequest.uri); + for (const auto& [key, value] : rustRequest.vars) { + request.getvars[std::string(key)] = std::string(value); + } + func(&request, &response); + fromCxxToRust(response, rustResponse); +} + +namespace pdns::rust::web::rec { + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define WRAPPER(A) void A(const Request& rustRequest, Response& rustResponse) { rustWrapper(::A, rustRequest, rustResponse); } + +void jsonstat(const Request& rustRequest, Response& rustResponse) +{ + rustWrapper(RecursorWebServer::jsonstat, rustRequest, rustResponse); +} + +WRAPPER(apiServerCacheFlush) +WRAPPER(apiServerDetail) +WRAPPER(apiServerZonesGET) +WRAPPER(apiServerZonesPOST) +WRAPPER(prometheusMetrics) +WRAPPER(serveStuff) +WRAPPER(apiServerStatistics) + } From 97a129176f6cf1dd35a78e70180195fde468bc94 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Fri, 22 Nov 2024 13:26:50 +0100 Subject: [PATCH 05/38] Do no start old webserver --- pdns/recursordist/rec-main.cc | 8 +++++--- pdns/recursordist/ws-recursor.cc | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index b25481c64eb2..f554811583d1 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -2919,7 +2919,7 @@ static void recursorThread() t_fdm->addReadFD(threadInfo.getPipes().readToThread, handlePipeRequest); if (threadInfo.isHandler()) { - if (::arg().mustDo("webserver")) { + if (false && ::arg().mustDo("webserver")) { SLOG(g_log << Logger::Warning << "Enabling web server" << endl, log->info(Logr::Info, "Enabling web server")); try { @@ -3320,8 +3320,10 @@ int main(int argc, char** argv) g_packetCache = std::make_unique(g_maxPacketCacheEntries, ::arg().asNum("packetcache-shards")); } - extern void serveRustWeb(); - serveRustWeb(); + if (::arg().mustDo("webserver")) { + extern void serveRustWeb(); + serveRustWeb(); + } ret = serviceMain(startupLog); } catch (const PDNSException& ae) { diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index afee7d182706..a2ad5e7c0c25 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -961,7 +961,8 @@ void serveRustWeb() for (const auto& [url, _] : g_urlmap) { urls.emplace_back(url); } - pdns::rust::web::rec::serveweb({"127.0.0.1:3000", "[::1]:3000"}, ::rust::Slice{urls.data(), urls.size()}); + auto address = ComboAddress(arg()["webserver-address"], arg().asNum("webserver-port")); + pdns::rust::web::rec::serveweb({::rust::String(address.toStringWithPort())}, ::rust::Slice{urls.data(), urls.size()}); } static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse) From 47f1f46125214d9e2c096ea4387f19ede25c1891 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Fri, 22 Nov 2024 14:12:28 +0100 Subject: [PATCH 06/38] Add missing paths (the ones do not work yet) --- pdns/recursordist/rec-web-stubs.hh | 23 +++- pdns/recursordist/settings/Makefile.am | 2 +- pdns/recursordist/settings/cxxsupport.cc | 1 - pdns/recursordist/settings/rust/Makefile.am | 2 +- pdns/recursordist/settings/rust/src/bridge.hh | 15 ++- pdns/recursordist/settings/rust/src/web.rs | 121 ++++++++++++++++++ pdns/recursordist/ws-recursor.cc | 42 ++++-- 7 files changed, 187 insertions(+), 19 deletions(-) diff --git a/pdns/recursordist/rec-web-stubs.hh b/pdns/recursordist/rec-web-stubs.hh index bf9e68ff0199..316d84dbabc8 100644 --- a/pdns/recursordist/rec-web-stubs.hh +++ b/pdns/recursordist/rec-web-stubs.hh @@ -1,15 +1,30 @@ -namespace pdns::rust::web::rec { +namespace pdns::rust::web::rec +{ // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define WRAPPER(A) void A(const Request& /* unused */ , Response& /* unused */) { } +#define WRAPPER(A) \ + void A(const Request& /* unused */, Response& /* unused */) {} -WRAPPER(jsonstat) +WRAPPER(apiDiscovery) +WRAPPER(apiDiscoveryV1) +WRAPPER(apiServer) WRAPPER(apiServerCacheFlush) +WRAPPER(apiServerConfig) +WRAPPER(apiServerConfigAllowFromGET) +WRAPPER(apiServerConfigAllowFromPUT) +WRAPPER(apiServerConfigAllowNotifyFromGET) +WRAPPER(apiServerConfigAllowNotifyFromPUT) WRAPPER(apiServerDetail) +WRAPPER(apiServerRPZStats) +WRAPPER(apiServerSearchData) +WRAPPER(apiServerStatistics) +WRAPPER(apiServerZoneDetailDELETE) +WRAPPER(apiServerZoneDetailGET) +WRAPPER(apiServerZoneDetailPUT) WRAPPER(apiServerZonesGET) WRAPPER(apiServerZonesPOST) +WRAPPER(jsonstat) WRAPPER(prometheusMetrics) WRAPPER(serveStuff) -WRAPPER(apiServerStatistics) #undef WRAPPER } diff --git a/pdns/recursordist/settings/Makefile.am b/pdns/recursordist/settings/Makefile.am index 7bfd21922a65..8d186d14aaed 100644 --- a/pdns/recursordist/settings/Makefile.am +++ b/pdns/recursordist/settings/Makefile.am @@ -22,7 +22,7 @@ BUILT_SOURCES=cxxsettings-generated.cc rust/src/lib.rs # with an rust/src/lib.rs.h that does not contain e.g. field name or field type changes. # # Use patterns to avoid having two instances of generate run simultaneously, a well-known hack for GNU make -cxxsettings-generated%cc rust/src/lib%rs rust/src/web%rs: table.py generate.py rust-preamble-in.rs rust-bridge-in.rs docs-old-preamble-in.rst docs-new-preamble-in.rst +cxxsettings-generated%cc rust/src/lib%rs: table.py generate.py rust-preamble-in.rs rust-bridge-in.rs docs-old-preamble-in.rst docs-new-preamble-in.rst @if test "$(PYTHON)" = ":"; then echo "Settings table table.py has changed, python is needed to regenerate the related settings files but python was not found. Please install python and re-run configure"; exit 1; fi @if ! $(PYTHON) --version | grep -q "Python 3"; then echo $(PYTHON) should be at least version 3. Please install python 3 and re-run configure; exit 1; fi $(MAKE) -C rust clean diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/settings/cxxsupport.cc index 28bb3423f809..b6118e4634af 100644 --- a/pdns/recursordist/settings/cxxsupport.cc +++ b/pdns/recursordist/settings/cxxsupport.cc @@ -1454,4 +1454,3 @@ bool pdns::rust::settings::rec::isValidHostname(::rust::Str str) return false; } } - diff --git a/pdns/recursordist/settings/rust/Makefile.am b/pdns/recursordist/settings/rust/Makefile.am index 6299a9f51e02..6ade7921d08b 100644 --- a/pdns/recursordist/settings/rust/Makefile.am +++ b/pdns/recursordist/settings/rust/Makefile.am @@ -11,7 +11,7 @@ EXTRA_DIST = \ src/web.rs # should actually end up in a target specific dir... -libsettings.a lib.rs.h: src/web.rs src/bridge.rs src/lib.rs src/helpers.rs Cargo.toml Cargo.lock build.rs +libsettings.a lib.rs.h web.rs.h: src/web.rs src/bridge.rs src/lib.rs src/helpers.rs Cargo.toml Cargo.lock build.rs SYSCONFDIR=$(sysconfdir) NODCACHEDIRNOD=$(localstatedir)/nod NODCACHEDIRUDR=$(localstatedir)/udr $(CARGO) build --release $(RUST_TARGET) --target-dir=$(builddir)/target --manifest-path ${srcdir}/Cargo.toml cp target/$(RUSTC_TARGET_ARCH)/release/libsettings.a libsettings.a cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/settings/src/lib.rs.h lib.rs.h diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index 4ab8f387f8e9..bbaf43dc9deb 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -35,12 +35,25 @@ namespace pdns::rust::web::rec struct KeyValue; struct Request; struct Response; +void apiServer(const Request& rustRequest, Response& rustResponse); +void apiDiscovery(const Request& rustRequest, Response& rustResponse); +void apiDiscoveryV1(const Request& rustRequest, Response& rustResponse); void apiServerCacheFlush(const Request& rustRequest, Response& rustResponse); void apiServerDetail(const Request& rustRequest, Response& rustResponse); void apiServerStatistics(const Request& rustRequest, Response& rustResponse); -void apiServerZonesGET(const Request& rustRequest,Response& rustResponse); +void apiServerZonesGET(const Request& rustRequest, Response& rustResponse); void apiServerZonesPOST(const Request& rustRequest, Response& rustResponse); void prometheusMetrics(const Request& rustRequest, Response& rustResponse); void serveStuff(const Request& rustRequest, Response& rustResponse); void jsonstat(const Request& rustRequest, Response& rustResponse); +void apiServerConfigAllowFromPUT(const Request& rustRequest, Response& rustResponse); +void apiServerConfigAllowFromGET(const Request& rustRequest, Response& rustResponse); +void apiServerConfigAllowNotifyFromGET(const Request& rustRequest, Response& rustResponse); +void apiServerConfigAllowNotifyFromPUT(const Request& rustRequest, Response& rustResponse); +void apiServerConfig(const Request& rustRequest, Response& rustResponse); +void apiServerRPZStats(const Request& rustRequest, Response& rustResponse); +void apiServerSearchData(const Request& rustRequest, Response& rustResponse); +void apiServerZoneDetailGET(const Request& rustRequest, Response& rustResponse); +void apiServerZoneDetailPUT(const Request& rustRequest, Response& rustResponse); +void apiServerZoneDetailDELETE(const Request& rustRequest, Response& rustResponse); } diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index db56e23a7fe0..8151c1a3ac25 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -108,6 +108,7 @@ async fn hello( ); } (&Method::PUT, "/api/v1/servers/localhost/cache/flush") => { + request.body = rust_request.collect().await?.to_bytes().to_vec(); api_wrapper( rustweb::apiServerCacheFlush as Func, &request, @@ -115,6 +116,89 @@ async fn hello( headers, ); } + (&Method::PUT, "/api/v1/servers/localhost/config/allow-from") => { + request.body = rust_request.collect().await?.to_bytes().to_vec(); + api_wrapper( + rustweb::apiServerConfigAllowFromPUT as Func, + &request, + &mut response, + headers, + ); + } + (&Method::GET, "/api/v1/servers/localhost/config/allow-from") => { + api_wrapper( + rustweb::apiServerConfigAllowFromGET as Func, + &request, + &mut response, + headers, + ); + } + (&Method::PUT, "/api/v1/servers/localhost/config/allow-notify-from") => { + request.body = rust_request.collect().await?.to_bytes().to_vec(); + api_wrapper( + rustweb::apiServerConfigAllowNotifyFromPUT as Func, + &request, + &mut response, + headers, + ); + } + (&Method::GET, "/api/v1/servers/localhost/config/allow-notify-from") => { + api_wrapper( + rustweb::apiServerConfigAllowNotifyFromGET as Func, + &request, + &mut response, + headers, + ); + } + (&Method::GET, "/api/v1/servers/localhost/config") => { + api_wrapper( + rustweb::apiServerConfig as Func, + &request, + &mut response, + headers, + ); + } + (&Method::GET, "/api/v1/servers/localhost/rpzstatistics") => { + api_wrapper( + rustweb::apiServerRPZStats as Func, + &request, + &mut response, + headers, + ); + } + (&Method::GET, "/api/v1/servers/localhost/search-data") => { + api_wrapper( + rustweb::apiServerSearchData as Func, + &request, + &mut response, + headers, + ); + } + (&Method::GET, "/api/v1/servers/localhost/zones/") => { + api_wrapper( + rustweb::apiServerZoneDetailGET as Func, + &request, + &mut response, + headers, + ); + } + (&Method::PUT, "/api/v1/servers/localhost/zones/") => { + request.body = rust_request.collect().await?.to_bytes().to_vec(); + api_wrapper( + rustweb::apiServerZoneDetailPUT as Func, + &request, + &mut response, + headers, + ); + } + (&Method::DELETE, "/api/v1/servers/localhost/zones/") => { + api_wrapper( + rustweb::apiServerZoneDetailDELETE as Func, + &request, + &mut response, + headers, + ); + } (&Method::GET, "/api/v1/servers/localhost/statistics") => { api_wrapper( rustweb::apiServerStatistics as Func, @@ -148,6 +232,30 @@ async fn hello( headers, ); } + (&Method::GET, "/api/v1/servers") => { + api_wrapper( + rustweb::apiServer as Func, + &request, + &mut response, + headers, + ); + } + (&Method::GET, "/api/v1") => { + api_wrapper( + rustweb::apiDiscoveryV1 as Func, + &request, + &mut response, + headers, + ); + } + (&Method::GET, "/api") => { + api_wrapper( + rustweb::apiDiscovery as Func, + &request, + &mut response, + headers, + ); + } (&Method::GET, "/metrics") => { rustweb::prometheusMetrics(&request, &mut response).unwrap(); } @@ -287,9 +395,22 @@ mod rustweb { unsafe extern "C++" { include!("bridge.hh"); + fn apiDiscovery(request: &Request, response: &mut Response) -> Result<()>; + fn apiDiscoveryV1(request: &Request, response: &mut Response) -> Result<()>; + fn apiServer(request: &Request, response: &mut Response) -> Result<()>; fn apiServerCacheFlush(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerConfig(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerConfigAllowFromGET(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerConfigAllowFromPUT(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerConfigAllowNotifyFromGET(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerConfigAllowNotifyFromPUT(request: &Request, response: &mut Response) -> Result<()>; fn apiServerDetail(requst: &Request, response: &mut Response) -> Result<()>; + fn apiServerRPZStats(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerSearchData(request: &Request, response: &mut Response) -> Result<()>; fn apiServerStatistics(requst: &Request, response: &mut Response) -> Result<()>; + fn apiServerZoneDetailDELETE(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerZoneDetailGET(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerZoneDetailPUT(request: &Request, response: &mut Response) -> Result<()>; fn apiServerZonesGET(request: &Request, response: &mut Response) -> Result<()>; fn apiServerZonesPOST(requst: &Request, response: &mut Response) -> Result<()>; fn jsonstat(request: &Request, response: &mut Response) -> Result<()>; diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index a2ad5e7c0c25..b4fb291ea778 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -474,7 +474,6 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) resp->setJsonBody(doc); } - static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) { DNSName canon = apiNameToDNSName(req->getvars["domain"]); @@ -870,10 +869,8 @@ void AsyncWebServer::serveConnection(const std::shared_ptr& socket) cons yarl.initialize(&req); socket->setNonBlocking(); - const struct timeval timeout - { - g_networkTimeoutMsec / 1000, static_cast(g_networkTimeoutMsec) % 1000 * 1000 - }; + const struct timeval timeout{ + g_networkTimeoutMsec / 1000, static_cast(g_networkTimeoutMsec) % 1000 * 1000}; std::shared_ptr tlsCtx{nullptr}; if (d_loglevel > WebServer::LogLevel::None) { socket->getRemote(remote); @@ -958,7 +955,7 @@ void AsyncWebServer::go() void serveRustWeb() { static ::rust::Vec<::rust::String> urls; - for (const auto& [url, _] : g_urlmap) { + for (const auto& [url, _] : g_urlmap) { urls.emplace_back(url); } auto address = ComboAddress(arg()["webserver-address"], arg().asNum("webserver-port")); @@ -978,7 +975,7 @@ static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Res } } -static void rustWrapper(const std::function& func, const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustResponse) +static void rustWrapper(const std::function& func, const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustResponse) { HttpRequest request; HttpResponse response; @@ -987,26 +984,49 @@ static void rustWrapper(const std::function& for (const auto& [key, value] : rustRequest.vars) { request.getvars[std::string(key)] = std::string(value); } - func(&request, &response); + request.d_slog = g_slog; // XXX + response.d_slog = g_slog; // XXX + try { + func(&request, &response); + } + catch (HttpException& e) { + response.body = e.response().body; + response.status = e.response().status; + } fromCxxToRust(response, rustResponse); } -namespace pdns::rust::web::rec { +namespace pdns::rust::web::rec +{ // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define WRAPPER(A) void A(const Request& rustRequest, Response& rustResponse) { rustWrapper(::A, rustRequest, rustResponse); } +#define WRAPPER(A) \ + void A(const Request& rustRequest, Response& rustResponse) { rustWrapper(::A, rustRequest, rustResponse); } void jsonstat(const Request& rustRequest, Response& rustResponse) { rustWrapper(RecursorWebServer::jsonstat, rustRequest, rustResponse); } +WRAPPER(apiDiscovery) +WRAPPER(apiDiscoveryV1) +WRAPPER(apiServer) WRAPPER(apiServerCacheFlush) +WRAPPER(apiServerConfig) +WRAPPER(apiServerConfigAllowFromGET) +WRAPPER(apiServerConfigAllowFromPUT) +WRAPPER(apiServerConfigAllowNotifyFromGET) +WRAPPER(apiServerConfigAllowNotifyFromPUT) WRAPPER(apiServerDetail) +WRAPPER(apiServerRPZStats) +WRAPPER(apiServerSearchData) +WRAPPER(apiServerStatistics) +WRAPPER(apiServerZoneDetailDELETE) +WRAPPER(apiServerZoneDetailGET) +WRAPPER(apiServerZoneDetailPUT) WRAPPER(apiServerZonesGET) WRAPPER(apiServerZonesPOST) WRAPPER(prometheusMetrics) WRAPPER(serveStuff) -WRAPPER(apiServerStatistics) } From 7319430a2fcc0edf38317f86e9ee723bc792854a Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Fri, 22 Nov 2024 16:26:04 +0100 Subject: [PATCH 07/38] Better error handling --- pdns/recursordist/settings/rust/src/web.rs | 4 ++-- pdns/recursordist/ws-recursor.cc | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 8151c1a3ac25..d934f2145609 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -266,13 +266,13 @@ async fn hello( } let pos = urls.iter().position(|x| String::from("/") + x == path); if pos.is_none() { - println!("{} not found", path); + eprintln!("{} {} not found", rust_request.method(), path); } if rustweb::serveStuff(&request, &mut response).is_err() { // Return 404 not found response. response.status = StatusCode::NOT_FOUND.as_u16(); response.body = NOTFOUND.to_vec(); - println!("{} not found case 2", path); + eprintln!("{} {} not found case 2", rust_request.method(), path); } } } diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index b4fb291ea778..7a1a326198f9 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -993,6 +993,12 @@ static void rustWrapper(const std::function& response.body = e.response().body; response.status = e.response().status; } + catch (const ApiException & e) { + response.setErrorResult(e.what(), 422); + } + catch (const JsonException & e) { + response.setErrorResult(e.what(), 422); + } fromCxxToRust(response, rustResponse); } From ff0fb2957e79898089b4024f3bf33d3c760e06a1 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Fri, 22 Nov 2024 17:00:15 +0100 Subject: [PATCH 08/38] Avoid a data race reported by TSAN --- pdns/recursordist/rec-main.cc | 15 ++++++++------- pdns/recursordist/rec-main.hh | 2 +- pdns/recursordist/reczones.cc | 4 ++-- pdns/recursordist/ws-recursor.cc | 16 +++++++++------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index f554811583d1..bd71181692d4 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -111,7 +111,7 @@ std::set g_proxyProtocolExceptions; boost::optional g_dns64Prefix{boost::none}; DNSName g_dns64PrefixReverse; unsigned int g_maxChainLength; -std::shared_ptr g_initialDomainMap; // new threads needs this to be setup +LockGuarded> g_initialDomainMap; // new threads needs this to be setup std::shared_ptr g_initialAllowFrom; // new thread needs to be setup with this std::shared_ptr g_initialAllowNotifyFrom; // new threads need this to be setup std::shared_ptr g_initialAllowNotifyFor; // new threads need this to be setup @@ -351,6 +351,11 @@ int RecThreadInfo::runThreads(Logr::log_t log) info.setHandler(); info.start(currentThreadId, "web+stat", cpusMap, log); + if (::arg().mustDo("webserver")) { + extern void serveRustWeb(); + serveRustWeb(); + } + for (auto& tInfo : RecThreadInfo::infos()) { tInfo.thread.join(); if (tInfo.exitCode != 0) { @@ -2228,7 +2233,7 @@ static int serviceMain(Logr::log_t log) } g_networkTimeoutMsec = ::arg().asNum("network-timeout"); - std::tie(g_initialDomainMap, g_initialAllowNotifyFor) = parseZoneConfiguration(g_yamlSettings); + std::tie(*g_initialDomainMap.lock(), g_initialAllowNotifyFor) = parseZoneConfiguration(g_yamlSettings); g_latencyStatSize = ::arg().asNum("latency-statistic-size"); @@ -2828,7 +2833,7 @@ static void recursorThread() auto& threadInfo = RecThreadInfo::self(); { SyncRes tmp(g_now); // make sure it allocates tsstorage before we do anything, like primeHints or so.. - SyncRes::setDomainMap(g_initialDomainMap); + SyncRes::setDomainMap(*g_initialDomainMap.lock()); t_allowFrom = g_initialAllowFrom; t_allowNotifyFrom = g_initialAllowNotifyFrom; t_allowNotifyFor = g_initialAllowNotifyFor; @@ -3320,10 +3325,6 @@ int main(int argc, char** argv) g_packetCache = std::make_unique(g_maxPacketCacheEntries, ::arg().asNum("packetcache-shards")); } - if (::arg().mustDo("webserver")) { - extern void serveRustWeb(); - serveRustWeb(); - } ret = serviceMain(startupLog); } catch (const PDNSException& ae) { diff --git a/pdns/recursordist/rec-main.hh b/pdns/recursordist/rec-main.hh index 1d07b75f3d51..4effb058f280 100644 --- a/pdns/recursordist/rec-main.hh +++ b/pdns/recursordist/rec-main.hh @@ -228,7 +228,7 @@ extern std::atomic g_statsWanted; extern uint32_t g_disthashseed; extern int g_argc; extern char** g_argv; -extern std::shared_ptr g_initialDomainMap; // new threads needs this to be setup +extern LockGuarded> g_initialDomainMap; // new threads needs this to be setup extern std::shared_ptr g_initialAllowFrom; // new thread needs to be setup with this extern std::shared_ptr g_initialAllowNotifyFrom; // new threads need this to be setup extern std::shared_ptr g_initialAllowNotifyFor; // new threads need this to be setup diff --git a/pdns/recursordist/reczones.cc b/pdns/recursordist/reczones.cc index f03ea750746a..f892225d4e2f 100644 --- a/pdns/recursordist/reczones.cc +++ b/pdns/recursordist/reczones.cc @@ -217,8 +217,8 @@ string reloadZoneConfiguration(bool yaml) for (const auto& entry : oldAndNewDomains) { wipeCaches(entry, true, 0xffff); } - extern std::shared_ptr g_initialDomainMap; // XXX - g_initialDomainMap = newDomainMap; + extern LockGuarded> g_initialDomainMap; // XXX + *g_initialDomainMap.lock() = newDomainMap; return "ok\n"; } catch (const std::exception& e) { diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 7a1a326198f9..1e112647efc5 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -359,13 +359,14 @@ static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) Json document = req->json(); DNSName zonename = apiNameToDNSName(stringFromJson(document, "name")); - - const auto& iter = g_initialDomainMap->find(zonename); - if (iter != g_initialDomainMap->cend()) { - throw ApiException("Zone already exists"); + { + auto map = g_initialDomainMap.lock(); + const auto& iter = (*map)->find(zonename); + if (iter != (*map)->cend()) { + throw ApiException("Zone already exists"); + } + doCreateZone(document); } - - doCreateZone(document); reloadZoneConfiguration(g_yamlSettings); fillZone(zonename, resp); resp->status = 201; @@ -374,7 +375,8 @@ static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) static void apiServerZonesGET(HttpRequest* /* req */, HttpResponse* resp) { Json::array doc; - for (const auto& val : *g_initialDomainMap) { + auto lock = g_initialDomainMap.lock(); + for (const auto& val : **lock) { const SyncRes::AuthDomain& zone = val.second; Json::array servers; for (const auto& server : zone.d_servers) { From 28d2882c50d048b213fe29e2be8f41c2b367a625 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Mon, 25 Nov 2024 11:13:36 +0100 Subject: [PATCH 09/38] Use Arc instead of global static --- pdns/recursordist/settings/rust/src/web.rs | 370 ++++++++++----------- pdns/recursordist/ws-recursor.cc | 4 +- 2 files changed, 183 insertions(+), 191 deletions(-) diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index d934f2145609..06e355965377 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -1,3 +1,18 @@ +/* +TODO + +- Logging +- Table based routing including OPTIONS request handling +- Requests taking e.g. an +- ACLs +- Authorization +- Allow multipe listen addreses in settings (singlevalued right now) +- TLS? +- Code is now in settings dir. It's only possible to split the modules into separate Rust libs if we + use shared libs (in theory, I did not try). Currenlty all CXX using Rust cargo's must be compiled + as one and refer to a single static Rust runtime, +*/ + use std::net::SocketAddr; use bytes::Bytes; @@ -9,9 +24,10 @@ use hyper_util::rt::TokioIo; use tokio::net::TcpListener; use tokio::runtime::Builder; use tokio::task::JoinSet; - use std::io::ErrorKind; use std::str::FromStr; +use std::sync::Arc; +use tokio::sync::Mutex; type GenericError = Box; type MyResult = std::result::Result; @@ -28,17 +44,66 @@ fn full>(chunk: T) -> BoxBody { type Func = fn(&rustweb::Request, &mut rustweb::Response) -> Result<(), cxx::Exception>; fn api_wrapper( + ctx: &Context, handler: Func, request: &rustweb::Request, response: &mut rustweb::Response, + reqheaders: &header::HeaderMap, headers: &mut header::HeaderMap, ) { - response.status = StatusCode::OK.as_u16(); // 200; - // security headers + // security headers headers.insert( header::ACCESS_CONTROL_ALLOW_ORIGIN, header::HeaderValue::from_static("*"), ); + if ctx.api_key.is_empty() { + // XXX log + // Www-Authenticate: X-API-Key realm="PowerDNS" + let status = StatusCode::UNAUTHORIZED; + response.status = status.as_u16(); + headers.insert( + header::WWW_AUTHENTICATE, + header::HeaderValue::from_static("X-API-Key ream=\"PowerDNS\""), + ); + response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); + return; + } + + // XXX encrypted credentials handling, password handling! + let allow_password = false; + let mut auth_ok = false; + if let Some(api) = reqheaders.get("x-api-key") { + auth_ok = api.as_bytes() == ctx.api_key.as_bytes(); + println!("OK {}", auth_ok); + } + if !auth_ok { + for kv in &request.vars { + if kv.key == "x-api-key" && kv.value == ctx.api_key { + auth_ok = true; + break; + } + } + } + if !auth_ok && allow_password { + if !ctx.webserver_password.is_empty() { + //auth_ok = req->compareAuthorization(*d_webserverPassword); XXX + } else { + auth_ok = true; + } + } + if !auth_ok { + // XXX log + let status = StatusCode::UNAUTHORIZED; + response.status = status.as_u16(); + headers.insert( + header::WWW_AUTHENTICATE, + header::HeaderValue::from_static("X-API-Key ream=\"PowerDNS\""), + ); + response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); + return; + } + response.status = StatusCode::OK.as_u16(); // 200; + headers.insert( header::X_CONTENT_TYPE_OPTIONS, header::HeaderValue::from_static("nosniff"), @@ -63,15 +128,28 @@ fn api_wrapper( match handler(request, response) { Ok(_) => {} Err(_) => { - response.status = StatusCode::UNPROCESSABLE_ENTITY.as_u16(); // 422 + let status = StatusCode::UNPROCESSABLE_ENTITY; // 422 + response.status = status.as_u16(); + response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); } } } +struct Context { + urls: Vec, + api_key: String, + webserver_password: String, + counter: Mutex, +} + async fn hello( rust_request: Request, - urls: &[String], + ctx: Arc ) -> MyResult> { + { + let mut counter = ctx.counter.lock().await; + *counter += 1; + } let mut rust_response = Response::builder(); let mut vars: Vec = vec![]; if let Some(query) = rust_request.uri().query() { @@ -98,173 +176,55 @@ async fn hello( headers: vec![], }; let headers = rust_response.headers_mut().expect("no headers?"); - match (rust_request.method(), rust_request.uri().path()) { - (&Method::GET, "/jsonstat") => { - api_wrapper( - rustweb::jsonstat as Func, - &request, - &mut response, - headers, - ); - } - (&Method::PUT, "/api/v1/servers/localhost/cache/flush") => { - request.body = rust_request.collect().await?.to_bytes().to_vec(); - api_wrapper( - rustweb::apiServerCacheFlush as Func, - &request, - &mut response, - headers, - ); - } - (&Method::PUT, "/api/v1/servers/localhost/config/allow-from") => { - request.body = rust_request.collect().await?.to_bytes().to_vec(); - api_wrapper( - rustweb::apiServerConfigAllowFromPUT as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api/v1/servers/localhost/config/allow-from") => { - api_wrapper( - rustweb::apiServerConfigAllowFromGET as Func, - &request, - &mut response, - headers, - ); - } - (&Method::PUT, "/api/v1/servers/localhost/config/allow-notify-from") => { - request.body = rust_request.collect().await?.to_bytes().to_vec(); - api_wrapper( - rustweb::apiServerConfigAllowNotifyFromPUT as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api/v1/servers/localhost/config/allow-notify-from") => { - api_wrapper( - rustweb::apiServerConfigAllowNotifyFromGET as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api/v1/servers/localhost/config") => { - api_wrapper( - rustweb::apiServerConfig as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api/v1/servers/localhost/rpzstatistics") => { - api_wrapper( - rustweb::apiServerRPZStats as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api/v1/servers/localhost/search-data") => { - api_wrapper( - rustweb::apiServerSearchData as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api/v1/servers/localhost/zones/") => { - api_wrapper( - rustweb::apiServerZoneDetailGET as Func, - &request, - &mut response, - headers, - ); - } - (&Method::PUT, "/api/v1/servers/localhost/zones/") => { - request.body = rust_request.collect().await?.to_bytes().to_vec(); - api_wrapper( - rustweb::apiServerZoneDetailPUT as Func, - &request, - &mut response, - headers, - ); - } - (&Method::DELETE, "/api/v1/servers/localhost/zones/") => { - api_wrapper( - rustweb::apiServerZoneDetailDELETE as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api/v1/servers/localhost/statistics") => { - api_wrapper( - rustweb::apiServerStatistics as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api/v1/servers/localhost/zones") => { - api_wrapper( - rustweb::apiServerZonesGET as Func, - &request, - &mut response, - headers, - ); - } - (&Method::POST, "/api/v1/servers/localhost/zones") => { - request.body = rust_request.collect().await?.to_bytes().to_vec(); - api_wrapper( - rustweb::apiServerZonesPOST as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api/v1/servers/localhost") => { - api_wrapper( - rustweb::apiServerDetail as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api/v1/servers") => { - api_wrapper( - rustweb::apiServer as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api/v1") => { - api_wrapper( - rustweb::apiDiscoveryV1 as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/api") => { - api_wrapper( - rustweb::apiDiscovery as Func, - &request, - &mut response, - headers, - ); - } - (&Method::GET, "/metrics") => { - rustweb::prometheusMetrics(&request, &mut response).unwrap(); - } + let mut apifunc: Option = None; + let method = rust_request.method().to_owned(); + match (&method, rust_request.uri().path()) { + (&Method::GET, "/jsonstat") => + apifunc = Some(rustweb::jsonstat), + (&Method::PUT, "/api/v1/servers/localhost/cache/flush") => + apifunc = Some(rustweb::apiServerCacheFlush), + (&Method::PUT, "/api/v1/servers/localhost/config/allow-from") => + apifunc = Some(rustweb::apiServerConfigAllowFromPUT), + (&Method::GET, "/api/v1/servers/localhost/config/allow-from") => + apifunc = Some(rustweb::apiServerConfigAllowFromGET), + (&Method::PUT, "/api/v1/servers/localhost/config/allow-notify-from") => + apifunc = Some(rustweb::apiServerConfigAllowNotifyFromPUT), + (&Method::GET, "/api/v1/servers/localhost/config/allow-notify-from") => + apifunc = Some(rustweb::apiServerConfigAllowNotifyFromGET), + (&Method::GET, "/api/v1/servers/localhost/config") => + apifunc = Some(rustweb::apiServerConfig), + (&Method::GET, "/api/v1/servers/localhost/rpzstatistics") => + apifunc = Some(rustweb::apiServerRPZStats), + (&Method::GET, "/api/v1/servers/localhost/search-data") => + apifunc = Some(rustweb::apiServerSearchData), + (&Method::GET, "/api/v1/servers/localhost/zones/") => + apifunc = Some(rustweb::apiServerZoneDetailGET), + (&Method::PUT, "/api/v1/servers/localhost/zones/") => + apifunc = Some(rustweb::apiServerZoneDetailPUT), + (&Method::DELETE, "/api/v1/servers/localhost/zones/") => + apifunc = Some(rustweb::apiServerZoneDetailDELETE), + (&Method::GET, "/api/v1/servers/localhost/statistics") => + apifunc = Some(rustweb::apiServerStatistics), + (&Method::GET, "/api/v1/servers/localhost/zones") => + apifunc = Some(rustweb::apiServerZonesGET), + (&Method::POST, "/api/v1/servers/localhost/zones") => + apifunc = Some(rustweb::apiServerZonesPOST), + (&Method::GET, "/api/v1/servers/localhost") => + apifunc = Some(rustweb::apiServerDetail), + (&Method::GET, "/api/v1/servers") => + apifunc = Some(rustweb::apiServer), + (&Method::GET, "/api/v1") => + apifunc = Some(rustweb::apiDiscoveryV1), + (&Method::GET, "/api") => + apifunc = Some(rustweb::apiDiscovery), + (&Method::GET, "/metrics") => + rustweb::prometheusMetrics(&request, &mut response).unwrap(), _ => { let mut path = rust_request.uri().path(); if path == "/" { path = "/index.html"; } - let pos = urls.iter().position(|x| String::from("/") + x == path); + let pos = ctx.urls.iter().position(|x| String::from("/") + x == path); if pos.is_none() { eprintln!("{} {} not found", rust_request.method(), path); } @@ -276,52 +236,81 @@ async fn hello( } } } + if let Some(func) = apifunc { + let reqheaders = rust_request.headers().clone(); + if rust_request.method()== Method::POST || rust_request.method() == Method::PUT { + request.body = rust_request.collect().await?.to_bytes().to_vec(); + } + api_wrapper( + &ctx, + func, + &request, + &mut response, + &reqheaders, + headers, + ); + } + + let mut body = full(response.body); + if method == Method::HEAD { + body = full(vec!()); + } + let mut rust_response = rust_response .status(StatusCode::from_u16(response.status).unwrap()) - .body(full(response.body))?; + .body(body)?; for kv in response.headers { rust_response.headers_mut().insert( header::HeaderName::from_bytes(kv.key.as_bytes()).unwrap(), header::HeaderValue::from_str(kv.value.as_str()).unwrap(), ); } + + rust_response.headers_mut().insert( + header::CONNECTION, + header::HeaderValue::from_str("close").unwrap(), + ); Ok(rust_response) } -async fn serveweb_async(listener: TcpListener, urls: &'static [String]) -> MyResult<()> { - //let request_counter = Arc::new(AtomicUsize::new(0)); - /* - let fut = http1::Builder::new() - .serve_connection(move || { - service_fn(move |req| hello(req)) - }); - */ +async fn serveweb_async(listener: TcpListener, ctx: Arc) -> MyResult<()> { + // We start a loop to continuously accept incoming connections loop { + let ctx = Arc::clone(&ctx); + let ctx2 = Arc::clone(&ctx); let (stream, _) = listener.accept().await?; // Use an adapter to access something implementing `tokio::io` traits as if they implement // `hyper::rt` IO traits. let io = TokioIo::new(stream); let fut = - http1::Builder::new().serve_connection(io, service_fn(move |req| hello(req, urls))); + http1::Builder::new().serve_connection(io, service_fn(move |req| { + let ctx = Arc::clone(&ctx); + hello(req, ctx) + })); // Spawn a tokio task to serve multiple connections concurrently tokio::task::spawn(async move { // Finally, we bind the incoming connection to our `hello` service - if let Err(err) = /* http1::Builder::new() - // `service_fn` converts our function in a `Service` - .serve_connection(io, service_fn(|req| hello(req))) - */ - fut.await + if let Err(err) = fut.await { eprintln!("Error serving connection: {:?}", err); } }); + eprintln!("{}", ctx2.counter.lock().await); } } -pub fn serveweb(addresses: &Vec, urls: &'static [String]) -> Result<(), std::io::Error> { +pub fn serveweb(addresses: &Vec, urls: &[String], api_key: String, webserver_password: String) -> Result<(), std::io::Error> { + // Context (R/O for now) + let ctx = Arc::new(Context { + urls: urls.to_vec(), + api_key, + webserver_password, + counter: Mutex::new(0), + }); + let runtime = Builder::new_current_thread() .worker_threads(1) .thread_name("rec/web") @@ -342,11 +331,11 @@ pub fn serveweb(addresses: &Vec, urls: &'static [String]) -> Result<(), }; let listener = runtime.block_on(async { TcpListener::bind(addr).await }); - + let ctx = Arc::clone(&ctx); match listener { Ok(val) => { println!("Listening on {}", addr); - set.spawn_on(serveweb_async(val, urls), runtime.handle()); + set.spawn_on(serveweb_async(val, ctx), runtime.handle()); } Err(err) => { let msg = format!("Unable to bind web socket: {}", err); @@ -367,13 +356,13 @@ pub fn serveweb(addresses: &Vec, urls: &'static [String]) -> Result<(), } #[cxx::bridge(namespace = "pdns::rust::web::rec")] -/* - * Functions callable from C++ - */ mod rustweb { + /* + * Functions callable from C++ + */ extern "Rust" { - fn serveweb(addreses: &Vec, urls: &'static [String]) -> Result<()>; + fn serveweb(addreses: &Vec, urls: &[String], apikey: String, password: String) -> Result<()>; } struct KeyValue { @@ -393,6 +382,9 @@ mod rustweb { headers: Vec, } + /* + * Functions callable from Rust + */ unsafe extern "C++" { include!("bridge.hh"); fn apiDiscovery(request: &Request, response: &mut Response) -> Result<()>; diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 1e112647efc5..d6d8b681ac67 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -956,12 +956,12 @@ void AsyncWebServer::go() void serveRustWeb() { - static ::rust::Vec<::rust::String> urls; + ::rust::Vec<::rust::String> urls; for (const auto& [url, _] : g_urlmap) { urls.emplace_back(url); } auto address = ComboAddress(arg()["webserver-address"], arg().asNum("webserver-port")); - pdns::rust::web::rec::serveweb({::rust::String(address.toStringWithPort())}, ::rust::Slice{urls.data(), urls.size()}); + pdns::rust::web::rec::serveweb({::rust::String(address.toStringWithPort())}, ::rust::Slice{urls.data(), urls.size()}, arg()["api-key"], arg()["webserver-password"]); } static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse) From b518cb30f311fbc29fa67bcb7391384450becfc0 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Tue, 26 Nov 2024 08:58:44 +0100 Subject: [PATCH 10/38] Better routing --- pdns/credentials.cc | 2 + pdns/recursordist/settings/rust/src/bridge.hh | 3 + pdns/recursordist/settings/rust/src/web.rs | 211 ++++++++++++------ pdns/recursordist/ws-recursor.cc | 26 ++- 4 files changed, 163 insertions(+), 79 deletions(-) diff --git a/pdns/credentials.cc b/pdns/credentials.cc index ddc5add19bd2..062155326fa1 100644 --- a/pdns/credentials.cc +++ b/pdns/credentials.cc @@ -388,7 +388,9 @@ CredentialsHolder::~CredentialsHolder() bool CredentialsHolder::matches(const std::string& password) const { + cerr << "matches " << d_isHashed << ' ' << password << ' ' << d_credentials.getString() << endl; if (d_isHashed) { + cerr << "Case 1" << endl; return verifyPassword(d_credentials.getString(), d_salt, d_workFactor, d_parallelFactor, d_blockSize, password); } // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index bbaf43dc9deb..633035f2d664 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -22,6 +22,8 @@ #pragma once #include "rust/cxx.h" +#include "../../../credentials.hh" + namespace pdns::rust::settings::rec { @@ -32,6 +34,7 @@ void setThreadName(::rust::Str str); namespace pdns::rust::web::rec { +using CredentialsHolder = ::CredentialsHolder; struct KeyValue; struct Request; struct Response; diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 06e355965377..17eb746ad82c 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -3,14 +3,15 @@ TODO - Logging - Table based routing including OPTIONS request handling -- Requests taking e.g. an -- ACLs -- Authorization +- ACLs of webserver +- ACL handling; thread local does not work, see how domains are done +- Authorization: metrics and plain files (and more?) are not subject to password auth - Allow multipe listen addreses in settings (singlevalued right now) - TLS? - Code is now in settings dir. It's only possible to split the modules into separate Rust libs if we - use shared libs (in theory, I did not try). Currenlty all CXX using Rust cargo's must be compiled - as one and refer to a single static Rust runtime, + use shared libs (in theory, I did not try). Currently all CXX using Rust cargo's must be compiled + as one and refer to a single static Rust runtime +- Ripping out yahttp stuff, providing some basic classees only */ use std::net::SocketAddr; @@ -28,6 +29,7 @@ use std::io::ErrorKind; use std::str::FromStr; use std::sync::Arc; use tokio::sync::Mutex; +use base64::prelude::*; type GenericError = Box; type MyResult = std::result::Result; @@ -43,6 +45,51 @@ fn full>(chunk: T) -> BoxBody { type Func = fn(&rustweb::Request, &mut rustweb::Response) -> Result<(), cxx::Exception>; +fn compare_authorization(ctx: &Context, reqheaders: &header::HeaderMap) -> bool +{ + let mut auth_ok = false; + if !ctx.password_ch.is_null() { + if let Some(authorization) = reqheaders.get("authorization") { + let mut lcase = authorization.as_bytes().to_owned(); + lcase.make_ascii_lowercase(); + if lcase.starts_with(b"basic ") { + let cookie = &authorization.as_bytes()[6..]; + if let Ok(plain) = BASE64_STANDARD.decode(cookie) { + println!("plain {:?}", plain); + let mut split = plain.split(|i| *i == b':'); + println!("split {:?}", split); + if split.next().is_some() { + println!("split {:?}", split); + if let Some(split) = split.next() { + println!("split {:?}", split); + cxx::let_cxx_string!(s = &split); + auth_ok = ctx.password_ch.as_ref().unwrap().matches(&s); + println!("OK4 {}", auth_ok); + } + } + } + } + } + println!("OK5 {}", auth_ok); + } else { + auth_ok = true; + } + auth_ok +} + +fn unauthorized(response: &mut rustweb::Response, headers: &mut header::HeaderMap, auth: &str) +{ + // XXX log + let status = StatusCode::UNAUTHORIZED; + response.status = status.as_u16(); + let val = format!("{} realm=\"PowerDNS\"", auth); + headers.insert( + header::WWW_AUTHENTICATE, + header::HeaderValue::from_str(&val).unwrap(), + ); + response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); +} + fn api_wrapper( ctx: &Context, handler: Func, @@ -50,56 +97,47 @@ fn api_wrapper( response: &mut rustweb::Response, reqheaders: &header::HeaderMap, headers: &mut header::HeaderMap, + allow_password: bool ) { + // security headers headers.insert( header::ACCESS_CONTROL_ALLOW_ORIGIN, header::HeaderValue::from_static("*"), ); - if ctx.api_key.is_empty() { - // XXX log - // Www-Authenticate: X-API-Key realm="PowerDNS" - let status = StatusCode::UNAUTHORIZED; - response.status = status.as_u16(); - headers.insert( - header::WWW_AUTHENTICATE, - header::HeaderValue::from_static("X-API-Key ream=\"PowerDNS\""), - ); - response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); + if ctx.api_ch.is_null() { + unauthorized(response, headers, "X-API-Key"); return; } - // XXX encrypted credentials handling, password handling! - let allow_password = false; + // XXX AUDIT! let mut auth_ok = false; + println!("OK0 {}", auth_ok); if let Some(api) = reqheaders.get("x-api-key") { - auth_ok = api.as_bytes() == ctx.api_key.as_bytes(); - println!("OK {}", auth_ok); + cxx::let_cxx_string!(s = &api.as_bytes()); + auth_ok = ctx.api_ch.as_ref().unwrap().matches(&s); + println!("OK1 {}", auth_ok); } if !auth_ok { for kv in &request.vars { - if kv.key == "x-api-key" && kv.value == ctx.api_key { + cxx::let_cxx_string!(s = &kv.value); + if kv.key == "x-api-key" && ctx.api_ch.as_ref().unwrap().matches(&s) { auth_ok = true; + println!("OK2 {}", auth_ok); break; } } } + println!("OK3 {}", auth_ok); if !auth_ok && allow_password { - if !ctx.webserver_password.is_empty() { - //auth_ok = req->compareAuthorization(*d_webserverPassword); XXX - } else { - auth_ok = true; + auth_ok = compare_authorization(ctx, reqheaders); + if !auth_ok { + unauthorized(response, headers, "Basic"); + return; } } if !auth_ok { - // XXX log - let status = StatusCode::UNAUTHORIZED; - response.status = status.as_u16(); - headers.insert( - header::WWW_AUTHENTICATE, - header::HeaderValue::from_static("X-API-Key ream=\"PowerDNS\""), - ); - response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); + unauthorized(response, headers, "X-API-Key"); return; } response.status = StatusCode::OK.as_u16(); // 200; @@ -137,8 +175,8 @@ fn api_wrapper( struct Context { urls: Vec, - api_key: String, - webserver_password: String, + password_ch: cxx::UniquePtr, + api_ch: cxx::UniquePtr, counter: Mutex, } @@ -150,7 +188,6 @@ async fn hello( let mut counter = ctx.counter.lock().await; *counter += 1; } - let mut rust_response = Response::builder(); let mut vars: Vec = vec![]; if let Some(query) = rust_request.uri().query() { for (k, v) in form_urlencoded::parse(query.as_bytes()) { @@ -169,73 +206,89 @@ async fn hello( body: vec![], uri: rust_request.uri().to_string(), vars, + parameters: vec![], }; let mut response = rustweb::Response { status: 0, body: vec![], headers: vec![], }; - let headers = rust_response.headers_mut().expect("no headers?"); let mut apifunc: Option = None; let method = rust_request.method().to_owned(); - match (&method, rust_request.uri().path()) { - (&Method::GET, "/jsonstat") => - apifunc = Some(rustweb::jsonstat), - (&Method::PUT, "/api/v1/servers/localhost/cache/flush") => + let path: Vec<_> = rust_request.uri().path().split('/').skip(1).collect(); + let mut allow_password = false; + match (&method, &*path) { + (&Method::GET, ["jsonstat"]) => { + allow_password = true; + apifunc = Some(rustweb::jsonstat); + } + (&Method::PUT, ["api", "v1", "servers", "localhost", "cache", "flush"]) => apifunc = Some(rustweb::apiServerCacheFlush), - (&Method::PUT, "/api/v1/servers/localhost/config/allow-from") => + (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-from"]) => apifunc = Some(rustweb::apiServerConfigAllowFromPUT), - (&Method::GET, "/api/v1/servers/localhost/config/allow-from") => + (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-from"]) => apifunc = Some(rustweb::apiServerConfigAllowFromGET), - (&Method::PUT, "/api/v1/servers/localhost/config/allow-notify-from") => + (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) => apifunc = Some(rustweb::apiServerConfigAllowNotifyFromPUT), - (&Method::GET, "/api/v1/servers/localhost/config/allow-notify-from") => + (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) => apifunc = Some(rustweb::apiServerConfigAllowNotifyFromGET), - (&Method::GET, "/api/v1/servers/localhost/config") => + (&Method::GET, ["api", "v1", "servers", "localhost", "config"]) => apifunc = Some(rustweb::apiServerConfig), - (&Method::GET, "/api/v1/servers/localhost/rpzstatistics") => + (&Method::GET, ["api", "v1", "servers", "localhost", "rpzstatistics"]) => apifunc = Some(rustweb::apiServerRPZStats), - (&Method::GET, "/api/v1/servers/localhost/search-data") => + (&Method::GET, ["api", "v1", "servers", "localhost", "search-data"]) => apifunc = Some(rustweb::apiServerSearchData), - (&Method::GET, "/api/v1/servers/localhost/zones/") => - apifunc = Some(rustweb::apiServerZoneDetailGET), - (&Method::PUT, "/api/v1/servers/localhost/zones/") => - apifunc = Some(rustweb::apiServerZoneDetailPUT), - (&Method::DELETE, "/api/v1/servers/localhost/zones/") => - apifunc = Some(rustweb::apiServerZoneDetailDELETE), - (&Method::GET, "/api/v1/servers/localhost/statistics") => - apifunc = Some(rustweb::apiServerStatistics), - (&Method::GET, "/api/v1/servers/localhost/zones") => + (&Method::GET, ["api", "v1", "servers", "localhost", "zones", id]) => { + request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); + apifunc = Some(rustweb::apiServerZoneDetailGET); + } + (&Method::PUT, ["api", "v1", "servers", "localhost", "zones", id]) => { + request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); + apifunc = Some(rustweb::apiServerZoneDetailPUT); + } + (&Method::DELETE, ["api", "v1", "servers", "localhost", "zones", id]) => { + request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); + apifunc = Some(rustweb::apiServerZoneDetailDELETE); + } + (&Method::GET, ["api", "v1", "servers", "localhost", "statistics"]) => { + allow_password = true; + apifunc = Some(rustweb::apiServerStatistics); + } + (&Method::GET, ["api", "v1", "servers", "localhost", "zones"]) => apifunc = Some(rustweb::apiServerZonesGET), - (&Method::POST, "/api/v1/servers/localhost/zones") => + (&Method::POST, ["api", "v1", "servers", "localhost", "zones"]) => apifunc = Some(rustweb::apiServerZonesPOST), - (&Method::GET, "/api/v1/servers/localhost") => - apifunc = Some(rustweb::apiServerDetail), - (&Method::GET, "/api/v1/servers") => + (&Method::GET, ["api", "v1", "servers", "localhost"]) => { + allow_password = true; + apifunc = Some(rustweb::apiServerDetail); + } + (&Method::GET, ["api", "v1", "servers"]) => apifunc = Some(rustweb::apiServer), - (&Method::GET, "/api/v1") => + (&Method::GET, ["api", "v1"]) => apifunc = Some(rustweb::apiDiscoveryV1), - (&Method::GET, "/api") => + (&Method::GET, ["api"]) => apifunc = Some(rustweb::apiDiscovery), - (&Method::GET, "/metrics") => + (&Method::GET, ["metrics"]) => rustweb::prometheusMetrics(&request, &mut response).unwrap(), _ => { - let mut path = rust_request.uri().path(); - if path == "/" { - path = "/index.html"; + let mut uripath = rust_request.uri().path(); + if uripath == "/" { + uripath = "/index.html"; } - let pos = ctx.urls.iter().position(|x| String::from("/") + x == path); + let pos = ctx.urls.iter().position(|x| String::from("/") + x == uripath); if pos.is_none() { - eprintln!("{} {} not found", rust_request.method(), path); + eprintln!("{} {} not found", rust_request.method(), uripath); } if rustweb::serveStuff(&request, &mut response).is_err() { // Return 404 not found response. response.status = StatusCode::NOT_FOUND.as_u16(); response.body = NOTFOUND.to_vec(); - eprintln!("{} {} not found case 2", rust_request.method(), path); + eprintln!("{} {} not found case 2", rust_request.method(), uripath); } } } + let mut rust_response = Response::builder(); + if let Some(func) = apifunc { let reqheaders = rust_request.headers().clone(); if rust_request.method()== Method::POST || rust_request.method() == Method::PUT { @@ -247,7 +300,8 @@ async fn hello( &request, &mut response, &reqheaders, - headers, + rust_response.headers_mut().expect("no headers?"), + allow_password, ); } @@ -302,12 +356,12 @@ async fn serveweb_async(listener: TcpListener, ctx: Arc) -> MyResult<() } } -pub fn serveweb(addresses: &Vec, urls: &[String], api_key: String, webserver_password: String) -> Result<(), std::io::Error> { +pub fn serveweb(addresses: &Vec, urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr) -> Result<(), std::io::Error> { // Context (R/O for now) let ctx = Arc::new(Context { urls: urls.to_vec(), - api_key, - webserver_password, + password_ch, + api_ch, counter: Mutex::new(0), }); @@ -355,14 +409,20 @@ pub fn serveweb(addresses: &Vec, urls: &[String], api_key: String, webse Ok(()) } +unsafe impl Send for rustweb::CredentialsHolder {} +unsafe impl Sync for rustweb::CredentialsHolder {} + #[cxx::bridge(namespace = "pdns::rust::web::rec")] mod rustweb { + extern "C++" { + type CredentialsHolder; + } /* * Functions callable from C++ */ extern "Rust" { - fn serveweb(addreses: &Vec, urls: &[String], apikey: String, password: String) -> Result<()>; + fn serveweb(addreses: &Vec, urls: &[String], pwch: UniquePtr, apikeych: UniquePtr) -> Result<()>; } struct KeyValue { @@ -374,6 +434,7 @@ mod rustweb { body: Vec, uri: String, vars: Vec, + parameters: Vec, } struct Response { @@ -408,5 +469,7 @@ mod rustweb { fn jsonstat(request: &Request, response: &mut Response) -> Result<()>; fn prometheusMetrics(request: &Request, response: &mut Response) -> Result<()>; fn serveStuff(request: &Request, response: &mut Response) -> Result<()>; + + fn matches(self: &CredentialsHolder, str: &CxxString) -> bool; } } diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index d6d8b681ac67..74b9fec81069 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -185,8 +185,9 @@ static void apiServerConfigAllowNotifyFromPUT(HttpRequest* req, HttpResponse* re static void fillZone(const DNSName& zonename, HttpResponse* resp) { - auto iter = SyncRes::t_sstorage.domainmap->find(zonename); - if (iter == SyncRes::t_sstorage.domainmap->end()) { + auto lock = g_initialDomainMap.lock(); + auto iter = (*lock)->find(zonename); + if (iter == (*lock)->end()) { throw ApiException("Could not find domain '" + zonename.toLogString() + "'"); } @@ -215,7 +216,7 @@ static void fillZone(const DNSName& zonename, HttpResponse* resp) {"kind", zone.d_servers.empty() ? "Native" : "Forwarded"}, {"servers", servers}, {"recursion_desired", zone.d_servers.empty() ? false : zone.d_rdForward}, - {"notify_allowed", isAllowNotifyForZone(zonename)}, + //{"notify_allowed", isAllowNotifyForZone(zonename)}, {"records", records}}; resp->setJsonBody(doc); @@ -398,7 +399,8 @@ static void apiServerZonesGET(HttpRequest* /* req */, HttpResponse* resp) static inline DNSName findZoneById(HttpRequest* req) { auto zonename = apiZoneIdToName(req->parameters["id"]); - if (SyncRes::t_sstorage.domainmap->find(zonename) == SyncRes::t_sstorage.domainmap->end()) { + auto lock = g_initialDomainMap.lock(); + if ((*lock)->find(zonename) == (*lock)->end()) { throw ApiException("Could not find domain '" + zonename.toLogString() + "'"); } return zonename; @@ -961,7 +963,18 @@ void serveRustWeb() urls.emplace_back(url); } auto address = ComboAddress(arg()["webserver-address"], arg().asNum("webserver-port")); - pdns::rust::web::rec::serveweb({::rust::String(address.toStringWithPort())}, ::rust::Slice{urls.data(), urls.size()}, arg()["api-key"], arg()["webserver-password"]); + + auto passwordString = arg()["webserver-password"]; + std::unique_ptr password; + if (!passwordString.empty()) { + password = std::make_unique(std::move(passwordString), arg().mustDo("webserver-hash-plaintext-credentials")); + } + auto apikeyString = arg()["api-key"]; + std::unique_ptr apikey; + if (!apikeyString.empty()) { + apikey = std::make_unique(std::move(apikeyString), arg().mustDo("webserver-hash-plaintext-credentials")); + } + pdns::rust::web::rec::serveweb({::rust::String(address.toStringWithPort())}, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey)); } static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse) @@ -986,6 +999,9 @@ static void rustWrapper(const std::function& for (const auto& [key, value] : rustRequest.vars) { request.getvars[std::string(key)] = std::string(value); } + for (const auto& [key, value] : rustRequest.parameters) { + request.parameters[std::string(key)] = std::string(value); + } request.d_slog = g_slog; // XXX response.d_slog = g_slog; // XXX try { From 12a49241ae43043339eb57f094ea7511dfcae8ee Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Wed, 27 Nov 2024 15:45:48 +0100 Subject: [PATCH 11/38] OPTIONS handling --- pdns/recursordist/settings/rust/src/web.rs | 231 +++++++++++++-------- pdns/recursordist/ws-recursor.cc | 2 +- 2 files changed, 148 insertions(+), 85 deletions(-) diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 17eb746ad82c..16b762b532cf 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -180,6 +180,122 @@ struct Context { counter: Mutex, } +fn file(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, response: &mut rustweb::Response) +{ + let mut uripath = path; + if uripath == "/" { + uripath = "/index.html"; + } + let pos = ctx.urls.iter().position(|x| String::from("/") + x == uripath); + if pos.is_none() { + eprintln!("{} {} not found", method, uripath); + } + + if rustweb::serveStuff(request, response).is_err() { + // Return 404 not found response. + response.status = StatusCode::NOT_FOUND.as_u16(); + response.body = NOTFOUND.to_vec(); + eprintln!("{} {} not found case 2", method, uripath); + } +} + +type FileFunc = fn(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, response: &mut rustweb::Response); + +fn matcher(method: &Method, path: &str, apifunc: &mut Option, rawfunc: &mut Option, filefunc: &mut Option, allow_password: &mut bool, request: &mut rustweb::Request) +{ + let path: Vec<_> = path.split('/').skip(1).collect(); + match (method, &*path) { + (&Method::GET, ["jsonstat"]) => { + *allow_password = true; + *apifunc = Some(rustweb::jsonstat); + } + (&Method::PUT, ["api", "v1", "servers", "localhost", "cache", "flush"]) => + *apifunc = Some(rustweb::apiServerCacheFlush), + (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-from"]) => + *apifunc = Some(rustweb::apiServerConfigAllowFromPUT), + (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-from"]) => + *apifunc = Some(rustweb::apiServerConfigAllowFromGET), + (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) => + *apifunc = Some(rustweb::apiServerConfigAllowNotifyFromPUT), + (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) => + *apifunc = Some(rustweb::apiServerConfigAllowNotifyFromGET), + (&Method::GET, ["api", "v1", "servers", "localhost", "config"]) => + *apifunc = Some(rustweb::apiServerConfig), + (&Method::GET, ["api", "v1", "servers", "localhost", "rpzstatistics"]) => + *apifunc = Some(rustweb::apiServerRPZStats), + (&Method::GET, ["api", "v1", "servers", "localhost", "search-data"]) => + *apifunc = Some(rustweb::apiServerSearchData), + (&Method::GET, ["api", "v1", "servers", "localhost", "zones", id]) => { + request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); + *apifunc = Some(rustweb::apiServerZoneDetailGET); + } + (&Method::PUT, ["api", "v1", "servers", "localhost", "zones", id]) => { + request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); + *apifunc = Some(rustweb::apiServerZoneDetailPUT); + } + (&Method::DELETE, ["api", "v1", "servers", "localhost", "zones", id]) => { + request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); + *apifunc = Some(rustweb::apiServerZoneDetailDELETE); + } + (&Method::GET, ["api", "v1", "servers", "localhost", "statistics"]) => { + *allow_password = true; + *apifunc = Some(rustweb::apiServerStatistics); + } + (&Method::GET, ["api", "v1", "servers", "localhost", "zones"]) => + *apifunc = Some(rustweb::apiServerZonesGET), + (&Method::POST, ["api", "v1", "servers", "localhost", "zones"]) => + *apifunc = Some(rustweb::apiServerZonesPOST), + (&Method::GET, ["api", "v1", "servers", "localhost"]) => { + *allow_password = true; + *apifunc = Some(rustweb::apiServerDetail); + } + (&Method::GET, ["api", "v1", "servers"]) => + *apifunc = Some(rustweb::apiServer), + (&Method::GET, ["api", "v1"]) => + *apifunc = Some(rustweb::apiDiscoveryV1), + (&Method::GET, ["api"]) => + *apifunc = Some(rustweb::apiDiscovery), + (&Method::GET, ["metrics"]) => + *rawfunc = Some(rustweb::prometheusMetrics), + _ => + *filefunc = Some(file), + } +} + +fn collect_options(path: &str, response: &mut rustweb::Response) +{ + let mut methods = vec!(); + for method in [Method::GET, Method::POST, Method::PUT, Method::DELETE] { + let mut apifunc: Option = None; + let mut rawfunc: Option<_> = None; + let mut filefunc: Option<_> = None; + let mut allow_password = false; + let mut request = rustweb::Request { + body: vec![], + uri: String::from(""), + vars: vec![], + parameters: vec![], + }; + println!("MATCH? {}", path); + matcher(&method, path, &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request); + if apifunc.is_some() || rawfunc.is_some() /* || filefunc.is_some() */ { + println!("MATCH"); + methods.push(method.to_string()); + } + } + if methods.is_empty() { + response.status = 404; + return; + } + response.status = 200; + methods.push(Method::OPTIONS.to_string()); + response.headers.push(rustweb::KeyValue{key: String::from("access-control-allow-origin"), value: String::from("*")}); + response.headers.push(rustweb::KeyValue{key: String::from("access-control-allow-headers"), value: String::from("Content-Type, X-API-Key")}); + response.headers.push(rustweb::KeyValue{key: String::from("access-control-max-age"), value: String::from("3600")}); + response.headers.push(rustweb::KeyValue{key: String::from("access-control-allow-methods"), value: methods.join(", ")}); + response.headers.push(rustweb::KeyValue{key: String::from("content-type"), value: String::from("text/plain")}); +} + async fn hello( rust_request: Request, ctx: Arc @@ -214,97 +330,44 @@ async fn hello( headers: vec![], }; let mut apifunc: Option = None; + let mut rawfunc: Option<_> = None; + let mut filefunc: Option<_> = None; let method = rust_request.method().to_owned(); - let path: Vec<_> = rust_request.uri().path().split('/').skip(1).collect(); let mut allow_password = false; - match (&method, &*path) { - (&Method::GET, ["jsonstat"]) => { - allow_password = true; - apifunc = Some(rustweb::jsonstat); - } - (&Method::PUT, ["api", "v1", "servers", "localhost", "cache", "flush"]) => - apifunc = Some(rustweb::apiServerCacheFlush), - (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-from"]) => - apifunc = Some(rustweb::apiServerConfigAllowFromPUT), - (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-from"]) => - apifunc = Some(rustweb::apiServerConfigAllowFromGET), - (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) => - apifunc = Some(rustweb::apiServerConfigAllowNotifyFromPUT), - (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) => - apifunc = Some(rustweb::apiServerConfigAllowNotifyFromGET), - (&Method::GET, ["api", "v1", "servers", "localhost", "config"]) => - apifunc = Some(rustweb::apiServerConfig), - (&Method::GET, ["api", "v1", "servers", "localhost", "rpzstatistics"]) => - apifunc = Some(rustweb::apiServerRPZStats), - (&Method::GET, ["api", "v1", "servers", "localhost", "search-data"]) => - apifunc = Some(rustweb::apiServerSearchData), - (&Method::GET, ["api", "v1", "servers", "localhost", "zones", id]) => { - request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); - apifunc = Some(rustweb::apiServerZoneDetailGET); - } - (&Method::PUT, ["api", "v1", "servers", "localhost", "zones", id]) => { - request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); - apifunc = Some(rustweb::apiServerZoneDetailPUT); - } - (&Method::DELETE, ["api", "v1", "servers", "localhost", "zones", id]) => { - request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); - apifunc = Some(rustweb::apiServerZoneDetailDELETE); - } - (&Method::GET, ["api", "v1", "servers", "localhost", "statistics"]) => { - allow_password = true; - apifunc = Some(rustweb::apiServerStatistics); - } - (&Method::GET, ["api", "v1", "servers", "localhost", "zones"]) => - apifunc = Some(rustweb::apiServerZonesGET), - (&Method::POST, ["api", "v1", "servers", "localhost", "zones"]) => - apifunc = Some(rustweb::apiServerZonesPOST), - (&Method::GET, ["api", "v1", "servers", "localhost"]) => { - allow_password = true; - apifunc = Some(rustweb::apiServerDetail); - } - (&Method::GET, ["api", "v1", "servers"]) => - apifunc = Some(rustweb::apiServer), - (&Method::GET, ["api", "v1"]) => - apifunc = Some(rustweb::apiDiscoveryV1), - (&Method::GET, ["api"]) => - apifunc = Some(rustweb::apiDiscovery), - (&Method::GET, ["metrics"]) => - rustweb::prometheusMetrics(&request, &mut response).unwrap(), - _ => { - let mut uripath = rust_request.uri().path(); - if uripath == "/" { - uripath = "/index.html"; - } - let pos = ctx.urls.iter().position(|x| String::from("/") + x == uripath); - if pos.is_none() { - eprintln!("{} {} not found", rust_request.method(), uripath); + let mut rust_response = Response::builder(); + + if method == &Method::OPTIONS { + collect_options(rust_request.uri().path(), &mut response); + } + else{ + matcher(&method, rust_request.uri().path(), &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request); + + if let Some(func) = apifunc { + let reqheaders = rust_request.headers().clone(); + if rust_request.method()== Method::POST || rust_request.method() == Method::PUT { + request.body = rust_request.collect().await?.to_bytes().to_vec(); } - if rustweb::serveStuff(&request, &mut response).is_err() { - // Return 404 not found response. - response.status = StatusCode::NOT_FOUND.as_u16(); - response.body = NOTFOUND.to_vec(); - eprintln!("{} {} not found case 2", rust_request.method(), uripath); + api_wrapper( + &ctx, + func, + &request, + &mut response, + &reqheaders, + rust_response.headers_mut().expect("no headers?"), + allow_password, + ); + } + else if let Some(func) = rawfunc { + if func(&request, &mut response).is_err() { + let status = StatusCode::UNPROCESSABLE_ENTITY; // 422 + response.status = status.as_u16(); + response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); } } - } - let mut rust_response = Response::builder(); - - if let Some(func) = apifunc { - let reqheaders = rust_request.headers().clone(); - if rust_request.method()== Method::POST || rust_request.method() == Method::PUT { - request.body = rust_request.collect().await?.to_bytes().to_vec(); + else if let Some(func) = filefunc { + func(&ctx, &method, rust_request.uri().path(), &request, &mut response); } - api_wrapper( - &ctx, - func, - &request, - &mut response, - &reqheaders, - rust_response.headers_mut().expect("no headers?"), - allow_password, - ); } - let mut body = full(response.body); if method == Method::HEAD { body = full(vec!()); diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 74b9fec81069..e5ac0e50dfb5 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -216,7 +216,7 @@ static void fillZone(const DNSName& zonename, HttpResponse* resp) {"kind", zone.d_servers.empty() ? "Native" : "Forwarded"}, {"servers", servers}, {"recursion_desired", zone.d_servers.empty() ? false : zone.d_rdForward}, - //{"notify_allowed", isAllowNotifyForZone(zonename)}, + {"notify_allowed", isAllowNotifyForZone(zonename)}, {"records", records}}; resp->setJsonBody(doc); From 6846c009dc873f0cf7e20eeab2dc3fd717e15d90 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Wed, 27 Nov 2024 16:50:24 +0100 Subject: [PATCH 12/38] API regression test succeed now, mostly setting right headers --- pdns/credentials.cc | 2 -- pdns/recursordist/rec-main.cc | 18 ++++++------ pdns/recursordist/rec-main.hh | 6 ++-- pdns/recursordist/reczones.cc | 2 ++ pdns/recursordist/settings/rust/src/web.rs | 16 +---------- pdns/recursordist/ws-recursor.cc | 32 ++++++++++++++++++---- 6 files changed, 41 insertions(+), 35 deletions(-) diff --git a/pdns/credentials.cc b/pdns/credentials.cc index 062155326fa1..ddc5add19bd2 100644 --- a/pdns/credentials.cc +++ b/pdns/credentials.cc @@ -388,9 +388,7 @@ CredentialsHolder::~CredentialsHolder() bool CredentialsHolder::matches(const std::string& password) const { - cerr << "matches " << d_isHashed << ' ' << password << ' ' << d_credentials.getString() << endl; if (d_isHashed) { - cerr << "Case 1" << endl; return verifyPassword(d_credentials.getString(), d_salt, d_workFactor, d_parallelFactor, d_blockSize, password); } // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index bd71181692d4..f1758e7082de 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -112,9 +112,9 @@ boost::optional g_dns64Prefix{boost::none}; DNSName g_dns64PrefixReverse; unsigned int g_maxChainLength; LockGuarded> g_initialDomainMap; // new threads needs this to be setup -std::shared_ptr g_initialAllowFrom; // new thread needs to be setup with this -std::shared_ptr g_initialAllowNotifyFrom; // new threads need this to be setup -std::shared_ptr g_initialAllowNotifyFor; // new threads need this to be setup +LockGuarded> g_initialAllowFrom; // new thread needs to be setup with this +LockGuarded> g_initialAllowNotifyFrom; // new threads need this to be setup +LockGuarded> g_initialAllowNotifyFor; // new threads need this to be setup bool g_logRPZChanges{false}; static time_t s_statisticsInterval; static std::atomic s_counter; @@ -1472,13 +1472,13 @@ void parseACLs() allowFrom = nullptr; } - g_initialAllowFrom = allowFrom; + *g_initialAllowFrom.lock() = allowFrom; // coverity[copy_constructor_call] maybe this can be avoided, but be careful as pointers get passed to other threads broadcastFunction([=] { return pleaseSupplantAllowFrom(allowFrom); }); auto allowNotifyFrom = parseACL("allow-notify-from-file", "allow-notify-from", log); - g_initialAllowNotifyFrom = allowNotifyFrom; + *g_initialAllowNotifyFrom.lock() = allowNotifyFrom; // coverity[copy_constructor_call] maybe this can be avoided, but be careful as pointers get passed to other threads broadcastFunction([=] { return pleaseSupplantAllowNotifyFrom(allowNotifyFrom); }); @@ -2233,7 +2233,7 @@ static int serviceMain(Logr::log_t log) } g_networkTimeoutMsec = ::arg().asNum("network-timeout"); - std::tie(*g_initialDomainMap.lock(), g_initialAllowNotifyFor) = parseZoneConfiguration(g_yamlSettings); + std::tie(*g_initialDomainMap.lock(), *g_initialAllowNotifyFor.lock()) = parseZoneConfiguration(g_yamlSettings); g_latencyStatSize = ::arg().asNum("latency-statistic-size"); @@ -2834,9 +2834,9 @@ static void recursorThread() { SyncRes tmp(g_now); // make sure it allocates tsstorage before we do anything, like primeHints or so.. SyncRes::setDomainMap(*g_initialDomainMap.lock()); - t_allowFrom = g_initialAllowFrom; - t_allowNotifyFrom = g_initialAllowNotifyFrom; - t_allowNotifyFor = g_initialAllowNotifyFor; + t_allowFrom = *g_initialAllowFrom.lock(); + t_allowNotifyFrom = *g_initialAllowNotifyFrom.lock(); + t_allowNotifyFor = *g_initialAllowNotifyFor.lock(); t_udpclientsocks = std::make_unique(); t_tcpClientCounts = std::make_unique(); if (g_proxyMapping) { diff --git a/pdns/recursordist/rec-main.hh b/pdns/recursordist/rec-main.hh index 4effb058f280..b1946b63e1a0 100644 --- a/pdns/recursordist/rec-main.hh +++ b/pdns/recursordist/rec-main.hh @@ -229,9 +229,9 @@ extern uint32_t g_disthashseed; extern int g_argc; extern char** g_argv; extern LockGuarded> g_initialDomainMap; // new threads needs this to be setup -extern std::shared_ptr g_initialAllowFrom; // new thread needs to be setup with this -extern std::shared_ptr g_initialAllowNotifyFrom; // new threads need this to be setup -extern std::shared_ptr g_initialAllowNotifyFor; // new threads need this to be setup +extern LockGuarded> g_initialAllowFrom; // new thread needs to be setup with this +extern LockGuarded> g_initialAllowNotifyFrom; // new threads need this to be setup +extern LockGuarded> g_initialAllowNotifyFor; // new threads need this to be setup extern thread_local std::shared_ptr t_traceRegex; extern thread_local FDWrapper t_tracefd; extern string g_programname; diff --git a/pdns/recursordist/reczones.cc b/pdns/recursordist/reczones.cc index f892225d4e2f..9410d7589f90 100644 --- a/pdns/recursordist/reczones.cc +++ b/pdns/recursordist/reczones.cc @@ -219,6 +219,8 @@ string reloadZoneConfiguration(bool yaml) } extern LockGuarded> g_initialDomainMap; // XXX *g_initialDomainMap.lock() = newDomainMap; + extern LockGuarded> g_initialAllowNotifyFor; // XXX + *g_initialAllowNotifyFor.lock() = newNotifySet; return "ok\n"; } catch (const std::exception& e) { diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 16b762b532cf..6e0e51966f91 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -2,9 +2,7 @@ TODO - Logging -- Table based routing including OPTIONS request handling - ACLs of webserver -- ACL handling; thread local does not work, see how domains are done - Authorization: metrics and plain files (and more?) are not subject to password auth - Allow multipe listen addreses in settings (singlevalued right now) - TLS? @@ -55,22 +53,16 @@ fn compare_authorization(ctx: &Context, reqheaders: &header::HeaderMap) -> bool if lcase.starts_with(b"basic ") { let cookie = &authorization.as_bytes()[6..]; if let Ok(plain) = BASE64_STANDARD.decode(cookie) { - println!("plain {:?}", plain); let mut split = plain.split(|i| *i == b':'); - println!("split {:?}", split); if split.next().is_some() { - println!("split {:?}", split); if let Some(split) = split.next() { - println!("split {:?}", split); cxx::let_cxx_string!(s = &split); auth_ok = ctx.password_ch.as_ref().unwrap().matches(&s); - println!("OK4 {}", auth_ok); } } } } } - println!("OK5 {}", auth_ok); } else { auth_ok = true; } @@ -112,23 +104,20 @@ fn api_wrapper( // XXX AUDIT! let mut auth_ok = false; - println!("OK0 {}", auth_ok); + if let Some(api) = reqheaders.get("x-api-key") { cxx::let_cxx_string!(s = &api.as_bytes()); auth_ok = ctx.api_ch.as_ref().unwrap().matches(&s); - println!("OK1 {}", auth_ok); } if !auth_ok { for kv in &request.vars { cxx::let_cxx_string!(s = &kv.value); if kv.key == "x-api-key" && ctx.api_ch.as_ref().unwrap().matches(&s) { auth_ok = true; - println!("OK2 {}", auth_ok); break; } } } - println!("OK3 {}", auth_ok); if !auth_ok && allow_password { auth_ok = compare_authorization(ctx, reqheaders); if !auth_ok { @@ -276,10 +265,8 @@ fn collect_options(path: &str, response: &mut rustweb::Response) vars: vec![], parameters: vec![], }; - println!("MATCH? {}", path); matcher(&method, path, &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request); if apifunc.is_some() || rawfunc.is_some() /* || filefunc.is_some() */ { - println!("MATCH"); methods.push(method.to_string()); } } @@ -415,7 +402,6 @@ async fn serveweb_async(listener: TcpListener, ctx: Arc) -> MyResult<() eprintln!("Error serving connection: {:?}", err); } }); - eprintln!("{}", ctx2.counter.lock().await); } } diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index e5ac0e50dfb5..d4449971fca7 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -91,11 +91,13 @@ static void apiServerConfigACLGET(const std::string& aclType, HttpRequest* /* re { // Return currently configured ACLs vector entries; - if (t_allowFrom && aclType == "allow-from") { - entries = t_allowFrom->toStringVector(); + auto lock1 = g_initialAllowFrom.lock(); + auto lock2 = g_initialAllowNotifyFrom.lock(); + if (*lock1 && aclType == "allow-from") { + entries = (*lock1)->toStringVector(); } - else if (t_allowNotifyFrom && aclType == "allow-notify-from") { - entries = t_allowNotifyFrom->toStringVector(); + else if (*lock2 && aclType == "allow-notify-from") { + entries = (*lock2)->toStringVector(); } resp->setJsonBody(Json::object{ @@ -183,6 +185,23 @@ static void apiServerConfigAllowNotifyFromPUT(HttpRequest* req, HttpResponse* re apiServerConfigACLPUT("allow-notify-from", req, resp); } +static bool isAllowedNotify(DNSName qname) +{ + auto lock = g_initialAllowNotifyFor.lock(); + + if (*lock == nullptr || (*lock)->empty()) { + return false; + } + + do { + auto ret = (*lock)->find(qname); + if (ret != (*lock)->end()) { + return true; + } + } while (qname.chopOff()); + return false; +} + static void fillZone(const DNSName& zonename, HttpResponse* resp) { auto lock = g_initialDomainMap.lock(); @@ -216,7 +235,7 @@ static void fillZone(const DNSName& zonename, HttpResponse* resp) {"kind", zone.d_servers.empty() ? "Native" : "Forwarded"}, {"servers", servers}, {"recursion_desired", zone.d_servers.empty() ? false : zone.d_rdForward}, - {"notify_allowed", isAllowNotifyForZone(zonename)}, + {"notify_allowed", isAllowedNotify(zonename)}, {"records", records}}; resp->setJsonBody(doc); @@ -444,8 +463,9 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) throw ApiException("Query q can't be blank"); } + auto lock = g_initialDomainMap.lock(); Json::array doc; - for (const SyncRes::domainmap_t::value_type& val : *SyncRes::t_sstorage.domainmap) { + for (const SyncRes::domainmap_t::value_type& val : **lock) { string zoneId = apiZoneNameToId(val.first); string zoneName = val.first.toString(); if (pdns_ci_find(zoneName, qVar) != string::npos) { From de0d13874d30497d4c686cfd60c77e126685e215 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Fri, 29 Nov 2024 14:19:34 +0100 Subject: [PATCH 13/38] Implement webserver acl --- pdns/recursordist/settings/cxxsupport.cc | 38 +++++++++++++++++++ pdns/recursordist/settings/rust/build.rs | 1 + pdns/recursordist/settings/rust/src/bridge.hh | 28 +++++++++++++- pdns/recursordist/settings/rust/src/web.rs | 29 ++++++++++++-- pdns/recursordist/ws-recursor.cc | 7 +++- 5 files changed, 96 insertions(+), 7 deletions(-) diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/settings/cxxsupport.cc index b6118e4634af..bd5084b6cefb 100644 --- a/pdns/recursordist/settings/cxxsupport.cc +++ b/pdns/recursordist/settings/cxxsupport.cc @@ -39,6 +39,8 @@ #include "base64.hh" #include "validate-recursor.hh" #include "threadname.hh" +#include "iputils.hh" +#include "bridge.hh" ::rust::Vec<::rust::String> pdns::settings::rec::getStrings(const std::string& name) { @@ -1454,3 +1456,39 @@ bool pdns::rust::settings::rec::isValidHostname(::rust::Str str) return false; } } + +namespace pdns::rust::web::rec +{ +NetmaskGroup::NetmaskGroup(const ::NetmaskGroup& arg) : + d_ptr(std::make_unique<::NetmaskGroup>(arg)) +{ +} +NetmaskGroup::~NetmaskGroup() = default; + + +ComboAddress::ComboAddress(const ::ComboAddress& arg) : + d_ptr(std::make_unique<::ComboAddress>(arg)) +{ +} +ComboAddress::~ComboAddress() = default; + +std::unique_ptr comboaddress(::rust::Str str) +{ + return std::make_unique(::ComboAddress(std::string(str))); +} + +[[nodiscard]] const ::NetmaskGroup& NetmaskGroup::get() const +{ + return *d_ptr; +} + +[[nodiscard]] const ::ComboAddress& ComboAddress::get() const +{ + return *d_ptr; +} + +bool matches(const std::unique_ptr& nmg, const std::unique_ptr& address) +{ + return nmg->get().match(address->get()); +} +} diff --git a/pdns/recursordist/settings/rust/build.rs b/pdns/recursordist/settings/rust/build.rs index e0fdf17c9c53..cdf64ab167a5 100644 --- a/pdns/recursordist/settings/rust/build.rs +++ b/pdns/recursordist/settings/rust/build.rs @@ -4,5 +4,6 @@ fn main() { // .file("src/source.cc") Code callable from Rust is in ../cxxsupport.cc .flag_if_supported("-std=c++17") .flag("-Isrc") + .flag("-I../../..") .compile("settings"); } diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index 633035f2d664..2d9e04611cc7 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -22,8 +22,7 @@ #pragma once #include "rust/cxx.h" -#include "../../../credentials.hh" - +#include "credentials.hh" namespace pdns::rust::settings::rec { @@ -32,12 +31,35 @@ bool isValidHostname(::rust::Str str); void setThreadName(::rust::Str str); } +class NetmaskGroup; +union ComboAddress; + namespace pdns::rust::web::rec { using CredentialsHolder = ::CredentialsHolder; + //using NetmaskGroup = ::NetmaskGroup; struct KeyValue; struct Request; struct Response; +class NetmaskGroup +{ +public: + NetmaskGroup(const ::NetmaskGroup& arg); + ~NetmaskGroup(); + [[nodiscard]] const ::NetmaskGroup& get() const; +private: + std::unique_ptr<::NetmaskGroup> d_ptr; +}; +class ComboAddress +{ +public: + ComboAddress(const ::ComboAddress& arg); + ~ComboAddress(); + [[nodiscard]] const ::ComboAddress& get() const; +private: + std::unique_ptr<::ComboAddress> d_ptr; +}; + void apiServer(const Request& rustRequest, Response& rustResponse); void apiDiscovery(const Request& rustRequest, Response& rustResponse); void apiDiscoveryV1(const Request& rustRequest, Response& rustResponse); @@ -59,4 +81,6 @@ void apiServerSearchData(const Request& rustRequest, Response& rustResponse); void apiServerZoneDetailGET(const Request& rustRequest, Response& rustResponse); void apiServerZoneDetailPUT(const Request& rustRequest, Response& rustResponse); void apiServerZoneDetailDELETE(const Request& rustRequest, Response& rustResponse); +std::unique_ptr comboaddress(::rust::Str str); +bool matches(const std::unique_ptr& nmg, const std::unique_ptr& address); } diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 6e0e51966f91..c694d3e80dd9 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -166,6 +166,7 @@ struct Context { urls: Vec, password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, + acl: cxx::UniquePtr, counter: Mutex, } @@ -382,9 +383,22 @@ async fn serveweb_async(listener: TcpListener, ctx: Arc) -> MyResult<() // We start a loop to continuously accept incoming connections loop { let ctx = Arc::clone(&ctx); - let ctx2 = Arc::clone(&ctx); let (stream, _) = listener.accept().await?; + match stream.peer_addr() { + Ok(address) => { + eprintln!("Peer: {:?}", address); + let combo = rustweb::comboaddress(&address.to_string()); + if !rustweb::matches(&ctx.acl, &combo) { + eprintln!("No acl match! {:?}", address); + continue; + } + } + Err(err) => { + eprintln!("Can't get: {:?}", err); + continue; // If we can't determine the peer address, don't + } + } // Use an adapter to access something implementing `tokio::io` traits as if they implement // `hyper::rt` IO traits. let io = TokioIo::new(stream); @@ -405,12 +419,13 @@ async fn serveweb_async(listener: TcpListener, ctx: Arc) -> MyResult<() } } -pub fn serveweb(addresses: &Vec, urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr) -> Result<(), std::io::Error> { +pub fn serveweb(addresses: &Vec, urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr) -> Result<(), std::io::Error> { // Context (R/O for now) let ctx = Arc::new(Context { urls: urls.to_vec(), password_ch, api_ch, + acl, counter: Mutex::new(0), }); @@ -460,18 +475,22 @@ pub fn serveweb(addresses: &Vec, urls: &[String], password_ch: cxx::Uniq unsafe impl Send for rustweb::CredentialsHolder {} unsafe impl Sync for rustweb::CredentialsHolder {} +unsafe impl Send for rustweb::NetmaskGroup {} +unsafe impl Sync for rustweb::NetmaskGroup {} #[cxx::bridge(namespace = "pdns::rust::web::rec")] mod rustweb { extern "C++" { - type CredentialsHolder; + type CredentialsHolder; + type NetmaskGroup; + type ComboAddress; } /* * Functions callable from C++ */ extern "Rust" { - fn serveweb(addreses: &Vec, urls: &[String], pwch: UniquePtr, apikeych: UniquePtr) -> Result<()>; + fn serveweb(addreses: &Vec, urls: &[String], pwch: UniquePtr, apikeych: UniquePtr, acl: UniquePtr) -> Result<()>; } struct KeyValue { @@ -520,5 +539,7 @@ mod rustweb { fn serveStuff(request: &Request, response: &mut Response) -> Result<()>; fn matches(self: &CredentialsHolder, str: &CxxString) -> bool; + fn comboaddress(address: &str) -> UniquePtr; + fn matches(nmg: &UniquePtr, address: &UniquePtr) -> bool; // match is a keyword } } diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index d4449971fca7..10698e2b714e 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -46,6 +46,7 @@ #include "tcpiohandler.hh" #include "rec-main.hh" #include "settings/cxxsettings.hh" // IWYU pragma: keep, needed by included generated file +#include "settings/rust/src/bridge.hh" #include "settings/rust/web.rs.h" using json11::Json; @@ -994,7 +995,11 @@ void serveRustWeb() if (!apikeyString.empty()) { apikey = std::make_unique(std::move(apikeyString), arg().mustDo("webserver-hash-plaintext-credentials")); } - pdns::rust::web::rec::serveweb({::rust::String(address.toStringWithPort())}, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey)); + NetmaskGroup acl; + acl.toMasks(::arg()["webserver-allow-from"]); + auto aclPtr = std::make_unique(acl); + + pdns::rust::web::rec::serveweb({::rust::String(address.toStringWithPort())}, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr)); } static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse) From 47c9cf6417a97a8d63c9de846b4840e5d3b08dee Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Fri, 29 Nov 2024 14:49:13 +0100 Subject: [PATCH 14/38] Template for Wrapper classes --- pdns/recursordist/rec_control.cc | 1 - pdns/recursordist/settings/cxxsupport.cc | 28 +++++++-------- pdns/recursordist/settings/rust/src/bridge.hh | 35 ++++++++++--------- pdns/recursordist/settings/rust/src/web.rs | 3 ++ pdns/recursordist/ws-recursor.cc | 12 ++++--- 5 files changed, 41 insertions(+), 38 deletions(-) diff --git a/pdns/recursordist/rec_control.cc b/pdns/recursordist/rec_control.cc index a7936cfba5d1..cda2f939fa33 100644 --- a/pdns/recursordist/rec_control.cc +++ b/pdns/recursordist/rec_control.cc @@ -449,4 +449,3 @@ int main(int argc, char** argv) } } #include "rec-web-stubs.hh" - diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/settings/cxxsupport.cc index bd5084b6cefb..225e645f828f 100644 --- a/pdns/recursordist/settings/cxxsupport.cc +++ b/pdns/recursordist/settings/cxxsupport.cc @@ -1459,34 +1459,30 @@ bool pdns::rust::settings::rec::isValidHostname(::rust::Str str) namespace pdns::rust::web::rec { -NetmaskGroup::NetmaskGroup(const ::NetmaskGroup& arg) : - d_ptr(std::make_unique<::NetmaskGroup>(arg)) + +template +Wrapper::Wrapper(const M& arg) : + d_ptr(std::make_unique(arg)) { } -NetmaskGroup::~NetmaskGroup() = default; +template +Wrapper::~Wrapper() = default; -ComboAddress::ComboAddress(const ::ComboAddress& arg) : - d_ptr(std::make_unique<::ComboAddress>(arg)) +template +[[nodiscard]] const M& Wrapper::get() const { + return *d_ptr; } -ComboAddress::~ComboAddress() = default; + +template class Wrapper<::NetmaskGroup>; +template class Wrapper<::ComboAddress>; std::unique_ptr comboaddress(::rust::Str str) { return std::make_unique(::ComboAddress(std::string(str))); } -[[nodiscard]] const ::NetmaskGroup& NetmaskGroup::get() const -{ - return *d_ptr; -} - -[[nodiscard]] const ::ComboAddress& ComboAddress::get() const -{ - return *d_ptr; -} - bool matches(const std::unique_ptr& nmg, const std::unique_ptr& address) { return nmg->get().match(address->get()); diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index 2d9e04611cc7..03dca2e89d21 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -37,28 +37,31 @@ union ComboAddress; namespace pdns::rust::web::rec { using CredentialsHolder = ::CredentialsHolder; - //using NetmaskGroup = ::NetmaskGroup; +// using NetmaskGroup = ::NetmaskGroup; struct KeyValue; struct Request; struct Response; -class NetmaskGroup -{ -public: - NetmaskGroup(const ::NetmaskGroup& arg); - ~NetmaskGroup(); - [[nodiscard]] const ::NetmaskGroup& get() const; -private: - std::unique_ptr<::NetmaskGroup> d_ptr; -}; -class ComboAddress + +template +class Wrapper { public: - ComboAddress(const ::ComboAddress& arg); - ~ComboAddress(); - [[nodiscard]] const ::ComboAddress& get() const; + Wrapper(const A& arg); + ~Wrapper(); // out-of-line definition, to keep A opaque + + Wrapper() = delete; + Wrapper(const Wrapper&) = delete; + Wrapper(Wrapper&&) = delete; + Wrapper& operator=(const Wrapper&) = delete; + Wrapper& operator=(Wrapper&&) = delete; + + [[nodiscard]] const A& get() const; + private: - std::unique_ptr<::ComboAddress> d_ptr; + std::unique_ptr d_ptr; }; +using NetmaskGroup = Wrapper<::NetmaskGroup>; +using ComboAddress = Wrapper<::ComboAddress>; void apiServer(const Request& rustRequest, Response& rustResponse); void apiDiscovery(const Request& rustRequest, Response& rustResponse); @@ -81,6 +84,6 @@ void apiServerSearchData(const Request& rustRequest, Response& rustResponse); void apiServerZoneDetailGET(const Request& rustRequest, Response& rustResponse); void apiServerZoneDetailPUT(const Request& rustRequest, Response& rustResponse); void apiServerZoneDetailDELETE(const Request& rustRequest, Response& rustResponse); -std::unique_ptr comboaddress(::rust::Str str); +std::unique_ptr comboaddress(::rust::Str str); bool matches(const std::unique_ptr& nmg, const std::unique_ptr& address); } diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index c694d3e80dd9..96dc17ec5709 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -10,6 +10,9 @@ TODO use shared libs (in theory, I did not try). Currently all CXX using Rust cargo's must be compiled as one and refer to a single static Rust runtime - Ripping out yahttp stuff, providing some basic classees only +- Some classes (NetmaskGroup, ComboAddress) need a uniqueptr Wrapper to keep them opaque (iputils + cannot be included without big headages in bridge.hh at the moment). We could seperate + NetmaskGroup, but I expect ComboAddress to not work as it is union. */ use std::net::SocketAddr; diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 10698e2b714e..7de35f61a94e 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -95,7 +95,7 @@ static void apiServerConfigACLGET(const std::string& aclType, HttpRequest* /* re auto lock1 = g_initialAllowFrom.lock(); auto lock2 = g_initialAllowNotifyFrom.lock(); if (*lock1 && aclType == "allow-from") { - entries = (*lock1)->toStringVector(); + entries = (*lock1)->toStringVector(); } else if (*lock2 && aclType == "allow-notify-from") { entries = (*lock2)->toStringVector(); @@ -894,8 +894,10 @@ void AsyncWebServer::serveConnection(const std::shared_ptr& socket) cons yarl.initialize(&req); socket->setNonBlocking(); - const struct timeval timeout{ - g_networkTimeoutMsec / 1000, static_cast(g_networkTimeoutMsec) % 1000 * 1000}; + const struct timeval timeout + { + g_networkTimeoutMsec / 1000, static_cast(g_networkTimeoutMsec) % 1000 * 1000 + }; std::shared_ptr tlsCtx{nullptr}; if (d_loglevel > WebServer::LogLevel::None) { socket->getRemote(remote); @@ -1036,10 +1038,10 @@ static void rustWrapper(const std::function& response.body = e.response().body; response.status = e.response().status; } - catch (const ApiException & e) { + catch (const ApiException& e) { response.setErrorResult(e.what(), 422); } - catch (const JsonException & e) { + catch (const JsonException& e) { response.setErrorResult(e.what(), 422); } fromCxxToRust(response, rustResponse); From 75dd1746d3a7da874e0a4291cfbefb24609b37d2 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Fri, 29 Nov 2024 16:33:08 +0100 Subject: [PATCH 15/38] Also start web service in single thread case --- pdns/iputils.hh | 1 - pdns/recursordist/Makefile.am | 2 +- pdns/recursordist/rec-main.cc | 5 +++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pdns/iputils.hh b/pdns/iputils.hh index 714bbe5ac89d..48e963ae1ac7 100644 --- a/pdns/iputils.hh +++ b/pdns/iputils.hh @@ -27,7 +27,6 @@ #include #include #include -#include #include "pdnsexception.hh" #include "misc.hh" #include diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 5bf734313e72..0c57de01a597 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -542,9 +542,9 @@ rec_control_SOURCES = \ rcpgenerator.cc rcpgenerator.hh \ rec-lua-conf.cc rec-lua-conf.hh \ rec-system-resolve.cc rec-system-resolve.hh \ + rec-web-stubs.hh \ rec_channel.cc rec_channel.hh \ rec_control.cc \ - rec-web-stubs.hh \ settings/cxxsupport.cc \ sillyrecords.cc \ sortlist.cc sortlist.hh \ diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index f1758e7082de..7dfcec091fd5 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -273,6 +273,11 @@ int RecThreadInfo::runThreads(Logr::log_t log) taskInfo.start(currentThreadId, "task", cpusMap, log); } + if (::arg().mustDo("webserver")) { + extern void serveRustWeb(); + serveRustWeb(); + } + currentThreadId = 1; auto& info = RecThreadInfo::info(currentThreadId); info.setListener(); From e0a55dc373168222ed15bad0d236a7ce556a2a42 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Tue, 3 Dec 2024 13:31:53 +0100 Subject: [PATCH 16/38] Don't link with yahttp any more (header files still in use) --- ext/yahttp/yahttp/meson.build | 4 ++++ pdns/recursordist/Makefile.am | 1 - pdns/recursordist/ext/Makefile.am | 1 - pdns/recursordist/meson.build | 4 ++-- pdns/recursordist/rec-main.cc | 8 +++++--- pdns/recursordist/ws-recursor.cc | 20 ++++++++++---------- pdns/recursordist/ws-recursor.hh | 2 ++ pdns/webserver.cc | 7 +++++-- pdns/webserver.hh | 16 +++++++++++++--- 9 files changed, 41 insertions(+), 22 deletions(-) diff --git a/ext/yahttp/yahttp/meson.build b/ext/yahttp/yahttp/meson.build index 2704d4798cc7..b9eb5d570f8a 100644 --- a/ext/yahttp/yahttp/meson.build +++ b/ext/yahttp/yahttp/meson.build @@ -20,3 +20,7 @@ dep_yahttp = declare_dependency( link_with: lib_yahttp, include_directories: include_directories('..'), ) + +dep_yahttp_header_only = declare_dependency( + include_directories: include_directories('..'), +) diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 0c57de01a597..488fc8e926c7 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -252,7 +252,6 @@ endif CLEANFILES += lua.hpp pdns_recursor_LDADD = \ - $(YAHTTP_LIBS) \ $(JSON11_LIBS) \ $(LIBCRYPTO_LIBS) \ $(BOOST_CONTEXT_LIBS) \ diff --git a/pdns/recursordist/ext/Makefile.am b/pdns/recursordist/ext/Makefile.am index 65131d0b277e..2fb9da054d6f 100644 --- a/pdns/recursordist/ext/Makefile.am +++ b/pdns/recursordist/ext/Makefile.am @@ -1,6 +1,5 @@ SUBDIRS = \ arc4random \ - yahttp \ json11 \ probds diff --git a/pdns/recursordist/meson.build b/pdns/recursordist/meson.build index 81d740568fe5..5f276d25fc8e 100644 --- a/pdns/recursordist/meson.build +++ b/pdns/recursordist/meson.build @@ -312,7 +312,7 @@ deps = [ dep_libssl, dep_lua, dep_protozero, - dep_yahttp, + dep_yahttp_header_only, dep_htmlfiles, dep_dnstap, dep_libcurl, @@ -422,7 +422,7 @@ tools = { dep_nod, dep_lua, dep_protozero, - dep_yahttp, + dep_yahttp_header_only, dep_json11, dep_settings, dep_rust_settings, diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index 7dfcec091fd5..98dbff074d64 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -2923,13 +2923,14 @@ static void recursorThread() } t_fdm = unique_ptr(getMultiplexer(log)); - +#if 0 std::unique_ptr rws; - +#endif t_fdm->addReadFD(threadInfo.getPipes().readToThread, handlePipeRequest); if (threadInfo.isHandler()) { - if (false && ::arg().mustDo("webserver")) { +#if 0 + if (::arg().mustDo("webserver")) { SLOG(g_log << Logger::Warning << "Enabling web server" << endl, log->info(Logr::Info, "Enabling web server")); try { @@ -2941,6 +2942,7 @@ static void recursorThread() _exit(99); } } +#endif SLOG(g_log << Logger::Info << "Enabled '" << t_fdm->getName() << "' multiplexer" << endl, log->info(Logr::Info, "Enabled multiplexer", "name", Logging::Loggable(t_fdm->getName()))); } diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 7de35f61a94e..3181564100d4 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -19,9 +19,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifdef HAVE_CONFIG_H + #include "config.h" -#endif + #include "ws-recursor.hh" #include "json.hh" @@ -42,8 +42,6 @@ #include "logging.hh" #include "rec-lua-conf.hh" #include "rpzloader.hh" -#include "uuid-utils.hh" -#include "tcpiohandler.hh" #include "rec-main.hh" #include "settings/cxxsettings.hh" // IWYU pragma: keep, needed by included generated file #include "settings/rust/src/bridge.hh" @@ -639,6 +637,8 @@ const std::map MetricDefinitionStorage::d_metrics #include "rec-prometheus-gen.h" }; +#ifndef RUST_WS + constexpr bool CHECK_PROMETHEUS_METRICS = false; static void validatePrometheusMetrics() @@ -719,8 +719,9 @@ RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm) d_ws->registerWebHandler("/metrics", prometheusMetrics, "GET"); d_ws->go(); } +#endif // !RUST_WS -void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse* resp) +static void jsonstat(HttpRequest* req, HttpResponse* resp) { string command; @@ -833,6 +834,8 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse* resp) resp->setErrorResult("Command '" + command + "' not found", 404); } +#ifndef RUST_WS + void AsyncServerNewConnectionMT(void* arg) { auto* server = static_cast(arg); @@ -978,6 +981,7 @@ void AsyncWebServer::go() } server->asyncWaitForConnections(d_fdm, [this](const std::shared_ptr& socket) { serveConnection(socket); }); } +#endif // !RUST_WS void serveRustWeb() { @@ -1054,11 +1058,7 @@ namespace pdns::rust::web::rec #define WRAPPER(A) \ void A(const Request& rustRequest, Response& rustResponse) { rustWrapper(::A, rustRequest, rustResponse); } -void jsonstat(const Request& rustRequest, Response& rustResponse) -{ - rustWrapper(RecursorWebServer::jsonstat, rustRequest, rustResponse); -} - +WRAPPER(jsonstat) WRAPPER(apiDiscovery) WRAPPER(apiDiscoveryV1) WRAPPER(apiServer) diff --git a/pdns/recursordist/ws-recursor.hh b/pdns/recursordist/ws-recursor.hh index 18ab40b5b7fc..882d29ba4fb3 100644 --- a/pdns/recursordist/ws-recursor.hh +++ b/pdns/recursordist/ws-recursor.hh @@ -28,6 +28,7 @@ class HttpRequest; class HttpResponse; +#if 0 class AsyncServer : public Server { public: @@ -75,3 +76,4 @@ public: private: std::unique_ptr d_ws{nullptr}; }; +#endif diff --git a/pdns/webserver.cc b/pdns/webserver.cc index fea0724d5382..e6595d6fac7f 100644 --- a/pdns/webserver.cc +++ b/pdns/webserver.cc @@ -19,9 +19,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifdef HAVE_CONFIG_H + #include "config.h" -#endif + #include "utility.hh" #include "webserver.hh" #include "misc.hh" @@ -136,6 +136,8 @@ void HttpResponse::setSuccessResult(const std::string& message, const int status this->status = status_; } +#ifndef RUST_WS + static void bareHandlerWrapper(const WebServer::HandlerFunction& handler, YaHTTP::Request* req, YaHTTP::Response* resp) { // wrapper to convert from YaHTTP::* to our subclasses @@ -686,3 +688,4 @@ void WebServer::go() } _exit(1); } +#endif // !RUST_WS diff --git a/pdns/webserver.hh b/pdns/webserver.hh index e1f3795f6912..e542bc54f9b1 100644 --- a/pdns/webserver.hh +++ b/pdns/webserver.hh @@ -20,9 +20,13 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once -#include -#include -#include + +#ifdef RECURSOR +// Network facing/routing part of webserver is implemented in rust. We stil use a few classes from +// yahttp, but do not link to it. +#define RUST_WS +#endif + #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Woverloaded-virtual" @@ -34,7 +38,9 @@ #include "credentials.hh" #include "namespaces.hh" +#ifndef REST_WS #include "sstuff.hh" +#endif #include "logging.hh" class HttpRequest : public YaHTTP::Request { @@ -159,6 +165,8 @@ public: } }; +#ifndef RUST_WS + class Server { public: @@ -300,3 +308,5 @@ protected: // Describes the amount of logging the webserver does WebServer::LogLevel d_loglevel{WebServer::LogLevel::Detailed}; }; + +#endif // !RUST_WS From 3237ebc9e1048809d82d7e4806953304f7124f9b Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Mon, 9 Dec 2024 16:07:53 +0100 Subject: [PATCH 17/38] AlLow multiple listen addresses in config --- pdns/recursordist/rec-main.hh | 1 + pdns/recursordist/settings/rust/src/web.rs | 55 ++++++++++++++-------- pdns/recursordist/settings/table.py | 13 +++++ pdns/recursordist/ws-recursor.cc | 12 ++++- 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/pdns/recursordist/rec-main.hh b/pdns/recursordist/rec-main.hh index b1946b63e1a0..4fc3e3a6fbf8 100644 --- a/pdns/recursordist/rec-main.hh +++ b/pdns/recursordist/rec-main.hh @@ -193,6 +193,7 @@ using RemoteLoggerStats_t = std::unordered_map g_yamlStruct; extern bool g_logCommonErrors; extern size_t g_proxyProtocolMaximumSize; extern std::atomic g_quiet; diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 96dc17ec5709..50f3635d952c 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -1,18 +1,17 @@ /* TODO - +- Table based routing? - Logging -- ACLs of webserver -- Authorization: metrics and plain files (and more?) are not subject to password auth -- Allow multipe listen addreses in settings (singlevalued right now) +- Authorization: metrics and plain files (and more?) are not subject to password auth plus the code needs a n careful audit. - TLS? - Code is now in settings dir. It's only possible to split the modules into separate Rust libs if we use shared libs (in theory, I did not try). Currently all CXX using Rust cargo's must be compiled as one and refer to a single static Rust runtime -- Ripping out yahttp stuff, providing some basic classees only -- Some classes (NetmaskGroup, ComboAddress) need a uniqueptr Wrapper to keep them opaque (iputils +- Ripping out yahttp stuff, providing some basic classes only. ATM we do use a few yahttp include files (but no .cc) +- Some classes (NetmaskGroup, ComboAddress) need a UniquePtr Wrapper to keep them opaque (iputils cannot be included without big headages in bridge.hh at the moment). We could seperate NetmaskGroup, but I expect ComboAddress to not work as it is union. +- Avoid unsafe? Can it be done? */ use std::net::SocketAddr; @@ -155,6 +154,7 @@ fn api_wrapper( header::HeaderValue::from_static("default-src 'self'; style-src 'self' 'unsafe-inline'"), ); + // This calls into C++ match handler(request, response) { Ok(_) => {} Err(_) => { @@ -165,6 +165,7 @@ fn api_wrapper( } } +// Data used by requests handlers, only counter is r/w. struct Context { urls: Vec, password_ch: cxx::UniquePtr, @@ -173,6 +174,7 @@ struct Context { counter: Mutex, } +// Serve a file fn file(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, response: &mut rustweb::Response) { let mut uripath = path; @@ -184,6 +186,7 @@ fn file(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, eprintln!("{} {} not found", method, uripath); } + // This calls into C++ if rustweb::serveStuff(request, response).is_err() { // Return 404 not found response. response.status = StatusCode::NOT_FOUND.as_u16(); @@ -194,6 +197,7 @@ fn file(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, type FileFunc = fn(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, response: &mut rustweb::Response); +// Match a request and return the function that imlements it, this should probably be table based. fn matcher(method: &Method, path: &str, apifunc: &mut Option, rawfunc: &mut Option, filefunc: &mut Option, allow_password: &mut bool, request: &mut rustweb::Request) { let path: Vec<_> = path.split('/').skip(1).collect(); @@ -255,6 +259,7 @@ fn matcher(method: &Method, path: &str, apifunc: &mut Option, rawfunc: &mu } } +// This constructs the answer to an OPTIONS query fn collect_options(path: &str, response: &mut rustweb::Response) { let mut methods = vec!(); @@ -287,14 +292,17 @@ fn collect_options(path: &str, response: &mut rustweb::Response) response.headers.push(rustweb::KeyValue{key: String::from("content-type"), value: String::from("text/plain")}); } -async fn hello( +// Main entry point after a request arrived +async fn process_request( rust_request: Request, ctx: Arc ) -> MyResult> { { + // For demo purposes let mut counter = ctx.counter.lock().await; *counter += 1; } + // Convert query part of URI into vars table let mut vars: Vec = vec![]; if let Some(query) = rust_request.uri().query() { for (k, v) in form_urlencoded::parse(query.as_bytes()) { @@ -309,6 +317,8 @@ async fn hello( vars.push(kv); } } + + // Fill request and response structs wih default values. let mut request = rustweb::Request { body: vec![], uri: rust_request.uri().to_string(), @@ -327,10 +337,11 @@ async fn hello( let mut allow_password = false; let mut rust_response = Response::builder(); - if method == &Method::OPTIONS { + if method == Method::OPTIONS { collect_options(rust_request.uri().path(), &mut response); } - else{ + else { + // Find the right fucntion implementing what the request wants matcher(&method, rust_request.uri().path(), &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request); if let Some(func) = apifunc { @@ -338,6 +349,7 @@ async fn hello( if rust_request.method()== Method::POST || rust_request.method() == Method::PUT { request.body = rust_request.collect().await?.to_bytes().to_vec(); } + // This calls indirectly into C++ api_wrapper( &ctx, func, @@ -349,6 +361,7 @@ async fn hello( ); } else if let Some(func) = rawfunc { + // Non-API func if func(&request, &mut response).is_err() { let status = StatusCode::UNPROCESSABLE_ENTITY; // 422 response.status = status.as_u16(); @@ -356,14 +369,17 @@ async fn hello( } } else if let Some(func) = filefunc { + // Server static file func(&ctx, &method, rust_request.uri().path(), &request, &mut response); } } + // Throw away body for HEAD call let mut body = full(response.body); if method == Method::HEAD { body = full(vec!()); } + // Construct response based on what C++ gave us let mut rust_response = rust_response .status(StatusCode::from_u16(response.status).unwrap()) .body(body)?; @@ -408,14 +424,13 @@ async fn serveweb_async(listener: TcpListener, ctx: Arc) -> MyResult<() let fut = http1::Builder::new().serve_connection(io, service_fn(move |req| { let ctx = Arc::clone(&ctx); - hello(req, ctx) + process_request(req, ctx) })); - // Spawn a tokio task to serve multiple connections concurrently + // Spawn a tokio task to serve the request tokio::task::spawn(async move { - // Finally, we bind the incoming connection to our `hello` service - if let Err(err) = fut.await - { + // Finally, we bind the incoming connection to our `process_request` service + if let Err(err) = fut.await { eprintln!("Error serving connection: {:?}", err); } }); @@ -423,26 +438,26 @@ async fn serveweb_async(listener: TcpListener, ctx: Arc) -> MyResult<() } pub fn serveweb(addresses: &Vec, urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr) -> Result<(), std::io::Error> { - // Context (R/O for now) + // Context, atomically reference counted let ctx = Arc::new(Context { urls: urls.to_vec(), password_ch, api_ch, acl, - counter: Mutex::new(0), + counter: Mutex::new(0), // more for educational purposes }); + // We use a single thread to handle all the requests, letting the runtime abstracts from this let runtime = Builder::new_current_thread() .worker_threads(1) .thread_name("rec/web") .enable_io() .build()?; + // For each listening address we spawn a tokio handler an then a single Posix thread is created that + // waits (forever) for all of them to complete by joining them all. let mut set = JoinSet::new(); - for addr_str in addresses { - // Socket create and bind should happen here - //let addr = SocketAddr::from_str(addr_str); let addr = match SocketAddr::from_str(addr_str) { Ok(val) => val, Err(err) => { @@ -476,6 +491,7 @@ pub fn serveweb(addresses: &Vec, urls: &[String], password_ch: cxx::Uniq Ok(()) } +// impl below needed because the classes are used in the Context, which gets passed around. unsafe impl Send for rustweb::CredentialsHolder {} unsafe impl Sync for rustweb::CredentialsHolder {} unsafe impl Send for rustweb::NetmaskGroup {} @@ -493,6 +509,7 @@ mod rustweb { * Functions callable from C++ */ extern "Rust" { + // The main entry point, This function will return, but will setup thread(s) to handle requests. fn serveweb(addreses: &Vec, urls: &[String], pwch: UniquePtr, apikeych: UniquePtr, acl: UniquePtr) -> Result<()>; } diff --git a/pdns/recursordist/settings/table.py b/pdns/recursordist/settings/table.py index 3c2a5a82ab87..06c65a985c7b 100644 --- a/pdns/recursordist/settings/table.py +++ b/pdns/recursordist/settings/table.py @@ -3203,6 +3203,19 @@ IP address for the webserver to listen on. ''', }, + { + 'name' : 'addresses', + 'section' : 'webservice', + 'type' : LType.ListSocketAddresses, + 'default' : '127.0.0.1:8082', + 'help' : 'IP Addresses of webserver to listen on', + 'doc' : ''' +IP addresses for the webserver to listen on. +If this setting has a non-default value, :ref:`setting-yaml-webservice.address` :ref:`setting-yaml-webservice.port` and will be ignored. + ''', + 'skip-old': 'No equivalent old-style setting', + 'versionadded': '5.3.0', + }, { 'name' : 'allow_from', 'section' : 'webservice', diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 3181564100d4..37a1ade4cac4 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -990,6 +990,14 @@ void serveRustWeb() urls.emplace_back(url); } auto address = ComboAddress(arg()["webserver-address"], arg().asNum("webserver-port")); + ::rust::Vec<::rust::String> addressList{address.toStringWithPort()}; + + if (g_yamlSettings) { + auto addresses = g_yamlStruct.lock()->webservice.addresses; + if (addresses.size() != 1 || addresses.at(0) != "127.0.0.1:8082") { + addressList = std::move(addresses); + } + } auto passwordString = arg()["webserver-password"]; std::unique_ptr password; @@ -1005,7 +1013,7 @@ void serveRustWeb() acl.toMasks(::arg()["webserver-allow-from"]); auto aclPtr = std::make_unique(acl); - pdns::rust::web::rec::serveweb({::rust::String(address.toStringWithPort())}, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr)); + pdns::rust::web::rec::serveweb(addressList, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr)); } static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse) @@ -1021,6 +1029,8 @@ static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Res } } + +// Convert what we receive from Rust into C++ data, call funtions and convert results back to Rust data static void rustWrapper(const std::function& func, const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustResponse) { HttpRequest request; From 1d20d866bad04a4e46c464eebc7df9a528104bae Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Tue, 21 Jan 2025 15:25:13 +0100 Subject: [PATCH 18/38] Basic tls support --- pdns/recursordist/rec-main.cc | 6 +- pdns/recursordist/reczones.cc | 20 +- pdns/recursordist/settings/generate.py | 11 +- pdns/recursordist/settings/rust-bridge-in.rs | 19 ++ pdns/recursordist/settings/rust/Cargo.lock | 173 ++++++++++++++- pdns/recursordist/settings/rust/Cargo.toml | 5 + .../recursordist/settings/rust/build_settings | 12 +- pdns/recursordist/settings/rust/src/bridge.hh | 3 + pdns/recursordist/settings/rust/src/bridge.rs | 21 ++ pdns/recursordist/settings/rust/src/web.rs | 202 +++++++++++++----- pdns/recursordist/settings/table.py | 10 +- pdns/recursordist/ws-recursor.cc | 32 ++- 12 files changed, 423 insertions(+), 91 deletions(-) diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index 98dbff074d64..b258792664d2 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -275,6 +275,7 @@ int RecThreadInfo::runThreads(Logr::log_t log) if (::arg().mustDo("webserver")) { extern void serveRustWeb(); + cerr << "CALL serveRustWeb" << endl; serveRustWeb(); } @@ -358,9 +359,12 @@ int RecThreadInfo::runThreads(Logr::log_t log) if (::arg().mustDo("webserver")) { extern void serveRustWeb(); + cerr << "WS is CALLED " << endl; serveRustWeb(); } - + else { + cerr << "WS is FALSE " << endl; + } for (auto& tInfo : RecThreadInfo::infos()) { tInfo.thread.join(); if (tInfo.exitCode != 0) { diff --git a/pdns/recursordist/reczones.cc b/pdns/recursordist/reczones.cc index 9410d7589f90..859894d70329 100644 --- a/pdns/recursordist/reczones.cc +++ b/pdns/recursordist/reczones.cc @@ -112,7 +112,6 @@ static void* pleaseUseNewSDomainsMap(std::shared_ptr newma string reloadZoneConfiguration(bool yaml) { - std::shared_ptr original = SyncRes::getDomainMap(); auto log = g_slog->withName("config"); string configname = ::arg()["config-dir"] + "/recursor"; @@ -199,17 +198,21 @@ string reloadZoneConfiguration(bool yaml) oldAndNewDomains.insert(entry.first); } - if (original) { - for (const auto& entry : *original) { - oldAndNewDomains.insert(entry.first); + extern LockGuarded> g_initialDomainMap; // XXX + { + auto lock = g_initialDomainMap.lock(); + if (*lock) { + for (const auto& entry : **lock) { + oldAndNewDomains.insert(entry.first); + } } } // these explicitly-named captures should not be necessary, as lambda // capture of tuple-like structured bindings is permitted, but some // compilers still don't allow it - broadcastFunction([dmap = std::move(newDomainMap)] { return pleaseUseNewSDomainsMap(dmap); }); - broadcastFunction([nsset = std::move(newNotifySet)] { return pleaseSupplantAllowNotifyFor(nsset); }); + broadcastFunction([dmap = newDomainMap] { return pleaseUseNewSDomainsMap(dmap); }); + broadcastFunction([nsset = newNotifySet] { return pleaseSupplantAllowNotifyFor(nsset); }); // Wipe the caches *after* the new auth domain info has been set // up, as a query during setting up might fill the caches @@ -217,10 +220,9 @@ string reloadZoneConfiguration(bool yaml) for (const auto& entry : oldAndNewDomains) { wipeCaches(entry, true, 0xffff); } - extern LockGuarded> g_initialDomainMap; // XXX - *g_initialDomainMap.lock() = newDomainMap; + *g_initialDomainMap.lock() = std::move(newDomainMap); extern LockGuarded> g_initialAllowNotifyFor; // XXX - *g_initialAllowNotifyFor.lock() = newNotifySet; + *g_initialAllowNotifyFor.lock() = std::move(newNotifySet); return "ok\n"; } catch (const std::exception& e) { diff --git a/pdns/recursordist/settings/generate.py b/pdns/recursordist/settings/generate.py index d1e53e26d07d..cf9dfd1ea8d7 100644 --- a/pdns/recursordist/settings/generate.py +++ b/pdns/recursordist/settings/generate.py @@ -100,6 +100,8 @@ class LType(Enum): ListDNSTapFrameStreamServers = auto() ListDNSTapNODFrameStreamServers = auto() ListForwardZones = auto() + ListForwardingCatalogZones = auto() + ListIncomingWSConfigs = auto() ListNegativeTrustAnchors = auto() ListProtobufServers = auto() ListProxyMappings = auto() @@ -110,7 +112,6 @@ class LType(Enum): ListSubnets = auto() ListTrustAnchors = auto() ListZoneToCaches = auto() - ListForwardingCatalogZones = auto() String = auto() Uint64 = auto() @@ -118,7 +119,7 @@ class LType(Enum): listOfStructuredTypes = (LType.ListAuthZones, LType.ListForwardZones, LType.ListTrustAnchors, LType.ListNegativeTrustAnchors, LType.ListProtobufServers, LType.ListDNSTapFrameStreamServers, LType.ListDNSTapNODFrameStreamServers, LType.ListSortLists, LType.ListRPZs, LType.ListZoneToCaches, LType.ListAllowedAdditionalQTypes, - LType.ListProxyMappings, LType.ListForwardingCatalogZones) + LType.ListProxyMappings, LType.ListForwardingCatalogZones, LType.ListIncomingWSConfigs) def get_olddoc_typename(typ): """Given a type from table.py, return the old-style type name""" @@ -140,7 +141,7 @@ def get_olddoc_typename(typ): return 'Comma separated list of \'zonename=IP\' pairs' if typ == LType.ListAuthZones: return 'Comma separated list of \'zonename=filename\' pairs' - return 'Unknown' + str(typ) + return 'Unknown1' + str(typ) def get_newdoc_typename(typ): """Given a type from table.py, return the new-style type name""" @@ -184,7 +185,7 @@ def get_newdoc_typename(typ): return 'Sequence of `ProxyMapping`_' if typ == LType.ListForwardingCatalogZones: return 'Sequence of `ForwardingCatalogZone`_' - return 'Unknown' + str(typ) + return 'Unknown2' + str(typ) def get_default_olddoc_value(typ, val): """Given a type and a value from table.py return the old doc representation of the value""" @@ -225,7 +226,7 @@ def list_to_base_type(typ): if typeName.startswith('List') and typeName.endswith('s'): baseName = typeName[4:len(typeName) - 1] return baseName - return 'Unknown: ' + typeName + return 'Unknown3: ' + typeName def get_rust_type(typ): """Determine which Rust type is used for a logical type""" diff --git a/pdns/recursordist/settings/rust-bridge-in.rs b/pdns/recursordist/settings/rust-bridge-in.rs index d43bfe502ecc..731cc15a8584 100644 --- a/pdns/recursordist/settings/rust-bridge-in.rs +++ b/pdns/recursordist/settings/rust-bridge-in.rs @@ -303,6 +303,25 @@ pub struct ForwardingCatalogZone { groups: Vec, } +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct IncomingTLS { + #[serde(default, skip_serializing_if = "crate::is_default")] + certificate: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + key: String, + #[serde(default, skip_serializing_if = "crate::is_default")] + password: String, +} +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct IncomingWSConfig { + #[serde(default, skip_serializing_if = "crate::is_default")] + addresses: Vec, + #[serde(default, skip_serializing_if = "crate::is_default")] + tls: IncomingTLS, +} + // Two structs used to generated YAML based on a vector of name to value mappings // Cannot use Enum as CXX has only very basic Enum support struct Value { diff --git a/pdns/recursordist/settings/rust/Cargo.lock b/pdns/recursordist/settings/rust/Cargo.lock index 6dc7ce9f4855..ecf54ad33ef9 100644 --- a/pdns/recursordist/settings/rust/Cargo.lock +++ b/pdns/recursordist/settings/rust/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -173,6 +173,17 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.31.1" @@ -254,6 +265,24 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", ] [[package]] @@ -263,12 +292,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", + "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", + "socket2", "tokio", + "tower-service", + "tracing", ] [[package]] @@ -396,12 +429,67 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustls" +version = "0.23.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.18" @@ -460,12 +548,17 @@ dependencies = [ "form_urlencoded", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "ipnet", "once_cell", + "rustls", + "rustls-pemfile", + "rustls-pki-types", "serde", "serde_yml", "tokio", + "tokio-rustls", ] [[package]] @@ -490,11 +583,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" -version = "2.0.77" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -524,6 +629,47 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -536,12 +682,27 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -638,3 +799,9 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/pdns/recursordist/settings/rust/Cargo.toml b/pdns/recursordist/settings/rust/Cargo.toml index 6765cbb99c08..f5170c5c2e14 100644 --- a/pdns/recursordist/settings/rust/Cargo.toml +++ b/pdns/recursordist/settings/rust/Cargo.toml @@ -21,6 +21,11 @@ http-body-util = "0.1" hyper-util = { version = "0.1", features = ["tokio"]} bytes = "1.8" form_urlencoded = "1.2" +hyper-rustls = { version = "0.27", default-features = false } +rustls = { version = "0.23", default-features = false, features = ["ring"] } +rustls-pemfile = "2.2" +pki-types = { package = "rustls-pki-types", version = "1.10" } +tokio-rustls = { version = "0.26", default-features = false } [build-dependencies] cxx-build = "1.0" diff --git a/pdns/recursordist/settings/rust/build_settings b/pdns/recursordist/settings/rust/build_settings index 831323850a60..34c6170de0b7 100755 --- a/pdns/recursordist/settings/rust/build_settings +++ b/pdns/recursordist/settings/rust/build_settings @@ -7,8 +7,10 @@ $CARGO build --release $RUST_TARGET --target-dir=$builddir/target --manifest-path $srcdir/Cargo.toml -cp -p target/$RUSTC_TARGET_ARCH/release/libsettings.a $builddir/settings/rust/libsettings.a -cp -p target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/lib.rs.h $srcdir/lib.rs.h -cp -p target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/lib.rs.h $builddir/settings/rust/lib.rs.h -cp -p target/$RUSTC_TARGET_ARCH/cxxbridge/rust/cxx.h $srcdir/cxx.h -cp -p target/$RUSTC_TARGET_ARCH/cxxbridge/rust/cxx.h $builddir/settings/rust/cxx.h +cp -vp target/$RUSTC_TARGET_ARCH/release/libsettings.a $builddir/settings/rust/libsettings.a +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/lib.rs.h $srcdir/lib.rs.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/lib.rs.h $builddir/settings/rust/lib.rs.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/rust/cxx.h $srcdir/cxx.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/rust/cxx.h $builddir/settings/rust/cxx.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/web.rs.h $srcdir/web.rs.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/web.rs.h $builddir/settings/rust/web.rs.h diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index 03dca2e89d21..3a33aab91ee2 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -21,6 +21,8 @@ */ #pragma once +#include + #include "rust/cxx.h" #include "credentials.hh" @@ -41,6 +43,7 @@ using CredentialsHolder = ::CredentialsHolder; struct KeyValue; struct Request; struct Response; +struct IncomingWSConfig; template class Wrapper diff --git a/pdns/recursordist/settings/rust/src/bridge.rs b/pdns/recursordist/settings/rust/src/bridge.rs index 83fc607bee9e..752b8830c850 100644 --- a/pdns/recursordist/settings/rust/src/bridge.rs +++ b/pdns/recursordist/settings/rust/src/bridge.rs @@ -88,6 +88,20 @@ impl Default for ForwardingCatalogZone { } } +impl Default for IncomingTLS { + fn default() -> Self { + let deserialized: IncomingTLS = serde_yaml::from_str("").unwrap(); + deserialized + } +} + +impl Default for IncomingWSConfig { + fn default() -> Self { + let deserialized: IncomingWSConfig = serde_yaml::from_str("").unwrap(); + deserialized + } +} + pub fn validate_socket_address(field: &str, val: &String) -> Result<(), ValidationError> { let sa = SocketAddr::from_str(val); if sa.is_err() { @@ -746,6 +760,13 @@ impl ForwardingCatalogZone { } } +impl IncomingWSConfig { + pub fn validate(&self, _field: &str) -> Result<(), ValidationError> { + // XXX + Ok(()) + } +} + #[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side. pub fn validate_auth_zones(field: &str, vec: &Vec) -> Result<(), ValidationError> { validate_vec(field, vec, |field, element| element.validate(field)) diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 50f3635d952c..e0a1852f6a49 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -397,47 +397,102 @@ async fn process_request( Ok(rust_response) } -async fn serveweb_async(listener: TcpListener, ctx: Arc) -> MyResult<()> { - - // We start a loop to continuously accept incoming connections - loop { - let ctx = Arc::clone(&ctx); - let (stream, _) = listener.accept().await?; - - match stream.peer_addr() { - Ok(address) => { - eprintln!("Peer: {:?}", address); - let combo = rustweb::comboaddress(&address.to_string()); - if !rustweb::matches(&ctx.acl, &combo) { - eprintln!("No acl match! {:?}", address); - continue; +async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::IncomingTLS, ctx: Arc) -> MyResult<()> { + + if !config.certificate.is_empty() { + let certs = load_certs(&config.certificate)?; + let key = load_private_key(&config.key)?; + let mut server_config = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(certs, key) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; + server_config.alpn_protocols = vec![b"http/1.1".to_vec(), b"http/1.0".to_vec()]; // b"h2".to_vec() + let tls_acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config)); + // We start a loop to continuously accept incoming connections + loop { + let ctx = Arc::clone(&ctx); + let (stream, _) = listener.accept().await?; + + match stream.peer_addr() { + Ok(address) => { + eprintln!("Peer: {:?}", address); + let combo = rustweb::comboaddress(&address.to_string()); + if !rustweb::matches(&ctx.acl, &combo) { + eprintln!("No acl match! {:?}", address); + continue; + } + } + Err(err) => { + eprintln!("Can't get: {:?}", err); + continue; // If we can't determine the peer address, don't } } - Err(err) => { - eprintln!("Can't get: {:?}", err); - continue; // If we can't determine the peer address, don't - } + // Use an adapter to access something implementing `tokio::io` traits as if they implement + // `hyper::rt` IO traits. + let tls_acceptor = tls_acceptor.clone(); + let tls_stream = match tls_acceptor.accept(stream).await { + Ok(tls_stream) => tls_stream, + Err(err) => { + eprintln!("failed to perform tls handshake: {err:#}"); + continue; + } + }; + let io = TokioIo::new(tls_stream); + let fut = + http1::Builder::new().serve_connection(io, service_fn(move |req| { + let ctx = Arc::clone(&ctx); + process_request(req, ctx) + })); + + // Spawn a tokio task to serve the request + tokio::task::spawn(async move { + // Finally, we bind the incoming connection to our `process_request` service + if let Err(err) = fut.await { + eprintln!("Error serving connection: {:?}", err); + } + }); } - // Use an adapter to access something implementing `tokio::io` traits as if they implement - // `hyper::rt` IO traits. - let io = TokioIo::new(stream); - let fut = - http1::Builder::new().serve_connection(io, service_fn(move |req| { - let ctx = Arc::clone(&ctx); - process_request(req, ctx) - })); - - // Spawn a tokio task to serve the request - tokio::task::spawn(async move { - // Finally, we bind the incoming connection to our `process_request` service - if let Err(err) = fut.await { - eprintln!("Error serving connection: {:?}", err); + } + else { + // We start a loop to continuously accept incoming connections + loop { + let ctx = Arc::clone(&ctx); + let (stream, _) = listener.accept().await?; + + match stream.peer_addr() { + Ok(address) => { + eprintln!("Peer: {:?}", address); + let combo = rustweb::comboaddress(&address.to_string()); + if !rustweb::matches(&ctx.acl, &combo) { + eprintln!("No acl match! {:?}", address); + continue; + } + } + Err(err) => { + eprintln!("Can't get: {:?}", err); + continue; // If we can't determine the peer address, don't + } } - }); + let io = TokioIo::new(stream); + let fut = + http1::Builder::new().serve_connection(io, service_fn(move |req| { + let ctx = Arc::clone(&ctx); + process_request(req, ctx) + })); + + // Spawn a tokio task to serve the request + tokio::task::spawn(async move { + // Finally, we bind the incoming connection to our `process_request` service + if let Err(err) = fut.await { + eprintln!("Error serving connection: {:?}", err); + } + }); + } } } -pub fn serveweb(addresses: &Vec, urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr) -> Result<(), std::io::Error> { +pub fn serveweb(incoming: &Vec, urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr) -> Result<(), std::io::Error> { + println!("SERVEWEB"); // Context, atomically reference counted let ctx = Arc::new(Context { urls: urls.to_vec(), @@ -457,25 +512,34 @@ pub fn serveweb(addresses: &Vec, urls: &[String], password_ch: cxx::Uniq // For each listening address we spawn a tokio handler an then a single Posix thread is created that // waits (forever) for all of them to complete by joining them all. let mut set = JoinSet::new(); - for addr_str in addresses { - let addr = match SocketAddr::from_str(addr_str) { - Ok(val) => val, - Err(err) => { - let msg = format!("`{}' is not a IP:port combination: {}", addr_str, err); - return Err(std::io::Error::new(ErrorKind::Other, msg)); - } - }; + for config in incoming { + println!("Config"); + for addr_str in &config.addresses { + println!("Config Addr {}", addr_str); + let addr = match SocketAddr::from_str(addr_str) { + Ok(val) => val, + Err(err) => { + let msg = format!("`{}' is not a IP:port combination: {}", addr_str, err); + return Err(std::io::Error::new(ErrorKind::Other, msg)); + } + }; - let listener = runtime.block_on(async { TcpListener::bind(addr).await }); - let ctx = Arc::clone(&ctx); - match listener { - Ok(val) => { - println!("Listening on {}", addr); - set.spawn_on(serveweb_async(val, ctx), runtime.handle()); - } - Err(err) => { - let msg = format!("Unable to bind web socket: {}", err); - return Err(std::io::Error::new(ErrorKind::Other, msg)); + let listener = runtime.block_on(async { TcpListener::bind(addr).await }); + let ctx = Arc::clone(&ctx); + match listener { + Ok(val) => { + let tls = crate::web::rustweb::IncomingTLS { + certificate: config.tls.certificate.clone(), + key: config.tls.key.clone(), + password: config.tls.password.clone(), + }; + println!("Listening on {}", addr); + set.spawn_on(serveweb_async(val, tls, ctx), runtime.handle()); + } + Err(err) => { + let msg = format!("Unable to bind web socket: {}", err); + return Err(std::io::Error::new(ErrorKind::Other, msg)); + } } } } @@ -491,6 +555,28 @@ pub fn serveweb(addresses: &Vec, urls: &[String], password_ch: cxx::Uniq Ok(()) } +// Load public certificate from file. +fn load_certs(filename: &str) -> std::io::Result>> { + // Open certificate file. + let certfile = std::fs::File::open(filename) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("failed to open {}: {}", filename, e)))?; + let mut reader = std::io::BufReader::new(certfile); + + // Load and return certificate. + rustls_pemfile::certs(&mut reader).collect() +} + +// Load private key from file. +fn load_private_key(filename: &str) -> std::io::Result> { + // Open keyfile. + let keyfile = std::fs::File::open(filename) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("failed to open {}: {}", filename, e)))?; + let mut reader = std::io::BufReader::new(keyfile); + + // Load and return a single private key. + rustls_pemfile::private_key(&mut reader).map(|key| key.unwrap()) +} + // impl below needed because the classes are used in the Context, which gets passed around. unsafe impl Send for rustweb::CredentialsHolder {} unsafe impl Sync for rustweb::CredentialsHolder {} @@ -505,12 +591,22 @@ mod rustweb { type ComboAddress; } + pub struct IncomingTLS { + certificate: String, + key: String, + password: String, + } + + struct IncomingWSConfig { + addresses: Vec, + tls: IncomingTLS, + } /* * Functions callable from C++ */ extern "Rust" { // The main entry point, This function will return, but will setup thread(s) to handle requests. - fn serveweb(addreses: &Vec, urls: &[String], pwch: UniquePtr, apikeych: UniquePtr, acl: UniquePtr) -> Result<()>; + fn serveweb(incoming: &Vec, urls: &[String], pwch: UniquePtr, apikeych: UniquePtr, acl: UniquePtr) -> Result<()>; } struct KeyValue { diff --git a/pdns/recursordist/settings/table.py b/pdns/recursordist/settings/table.py index 06c65a985c7b..dbdcd4867a8b 100644 --- a/pdns/recursordist/settings/table.py +++ b/pdns/recursordist/settings/table.py @@ -3204,13 +3204,13 @@ ''', }, { - 'name' : 'addresses', + 'name' : 'listen', 'section' : 'webservice', - 'type' : LType.ListSocketAddresses, - 'default' : '127.0.0.1:8082', - 'help' : 'IP Addresses of webserver to listen on', + 'type' : LType.ListIncomingWSConfigs, + 'default' : '', + 'help' : 'XXXX', 'doc' : ''' -IP addresses for the webserver to listen on. +XXXXX IP addresses for the webserver to listen on. If this setting has a non-default value, :ref:`setting-yaml-webservice.address` :ref:`setting-yaml-webservice.port` and will be ignored. ''', 'skip-old': 'No equivalent old-style setting', diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 37a1ade4cac4..1bd995ad6758 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -985,20 +985,31 @@ void AsyncWebServer::go() void serveRustWeb() { - ::rust::Vec<::rust::String> urls; - for (const auto& [url, _] : g_urlmap) { - urls.emplace_back(url); - } - auto address = ComboAddress(arg()["webserver-address"], arg().asNum("webserver-port")); - ::rust::Vec<::rust::String> addressList{address.toStringWithPort()}; + cerr << "SERVERUSTWEB" << endl; + ::rust::Vec config; if (g_yamlSettings) { - auto addresses = g_yamlStruct.lock()->webservice.addresses; - if (addresses.size() != 1 || addresses.at(0) != "127.0.0.1:8082") { - addressList = std::move(addresses); + auto settings = g_yamlStruct.lock(); + for (const auto& listen : settings->webservice.listen) { + pdns::rust::web::rec::IncomingWSConfig tmp; + for (const auto& address : listen.addresses) { + tmp.addresses.emplace_back(address); + } + tmp.tls = pdns::rust::web::rec::IncomingTLS{listen.tls.certificate, listen.tls.key, listen.tls.password}; + config.emplace_back(tmp); } } + cerr << "Config ahs " << config.size() << " entries" << endl; + if (config.empty()) { + auto address = ComboAddress(arg()["webserver-address"], arg().asNum("webserver-port")); + pdns::rust::web::rec::IncomingWSConfig tmp{{::rust::String{address.toStringWithPort()}}, {}}; + config.emplace_back(tmp); + } + ::rust::Vec<::rust::String> urls; + for (const auto& [url, _] : g_urlmap) { + urls.emplace_back(url); + } auto passwordString = arg()["webserver-password"]; std::unique_ptr password; if (!passwordString.empty()) { @@ -1013,7 +1024,8 @@ void serveRustWeb() acl.toMasks(::arg()["webserver-allow-from"]); auto aclPtr = std::make_unique(acl); - pdns::rust::web::rec::serveweb(addressList, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr)); + cerr << "CALL SERVEWEB" << endl; + pdns::rust::web::rec::serveweb(config, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr)); } static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse) From 479a5bc0b011be579b64b6a57dbc40ad21d1f0f2 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Mon, 27 Jan 2025 14:27:10 +0100 Subject: [PATCH 19/38] Logging --- pdns/recursordist/settings/cxxsupport.cc | 27 ++++ pdns/recursordist/settings/rust/Cargo.lock | 10 ++ pdns/recursordist/settings/rust/Cargo.toml | 2 +- pdns/recursordist/settings/rust/src/bridge.hh | 13 +- pdns/recursordist/settings/rust/src/web.rs | 133 ++++++++++++++---- pdns/recursordist/ws-recursor.cc | 6 +- 6 files changed, 158 insertions(+), 33 deletions(-) diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/settings/cxxsupport.cc index 225e645f828f..82dd3c8451af 100644 --- a/pdns/recursordist/settings/cxxsupport.cc +++ b/pdns/recursordist/settings/cxxsupport.cc @@ -41,6 +41,7 @@ #include "threadname.hh" #include "iputils.hh" #include "bridge.hh" +#include "settings/rust/web.rs.h" ::rust::Vec<::rust::String> pdns::settings::rec::getStrings(const std::string& name) { @@ -1477,6 +1478,7 @@ template template class Wrapper<::NetmaskGroup>; template class Wrapper<::ComboAddress>; +template class Wrapper>; std::unique_ptr comboaddress(::rust::Str str) { @@ -1487,4 +1489,29 @@ bool matches(const std::unique_ptr& nmg, const std::unique_ptrget().match(address->get()); } + +void log(const std::unique_ptr& logger, pdns::rust::web::rec::Priority log_level, ::rust::Str msg, const ::rust::Vec& values) +{ + auto log = logger->get(); + for (const auto& [key, value] : values) { + log = log->withValues(std::string(key), Logging::Loggable(std::string(value))); + } + log->info(static_cast(log_level), std::string(msg)); +} + + void error(const std::unique_ptr& logger, pdns::rust::web::rec::Priority log_level, ::rust::Str error, ::rust::Str msg, const ::rust::Vec& values) +{ + auto log = logger->get(); + for (const auto& [key, value] : values) { + log = log->withValues(std::string(key), Logging::Loggable(std::string(value))); + } + log->error(static_cast(log_level), std::string(error), std::string(msg)); +} + +std::unique_ptr withValue(const std::unique_ptr& logger, ::rust::Str key, ::rust::Str val) +{ + auto ret = logger->get()->withValues(std::string(key), Logging::Loggable(std::string(val))); + return std::make_unique(ret); +} + } diff --git a/pdns/recursordist/settings/rust/Cargo.lock b/pdns/recursordist/settings/rust/Cargo.lock index ecf54ad33ef9..4eca9bc1136e 100644 --- a/pdns/recursordist/settings/rust/Cargo.lock +++ b/pdns/recursordist/settings/rust/Cargo.lock @@ -559,6 +559,7 @@ dependencies = [ "serde_yml", "tokio", "tokio-rustls", + "uuid", ] [[package]] @@ -688,6 +689,15 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "uuid" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +dependencies = [ + "getrandom", +] + [[package]] name = "version_check" version = "0.9.5" diff --git a/pdns/recursordist/settings/rust/Cargo.toml b/pdns/recursordist/settings/rust/Cargo.toml index f5170c5c2e14..c68288d30301 100644 --- a/pdns/recursordist/settings/rust/Cargo.toml +++ b/pdns/recursordist/settings/rust/Cargo.toml @@ -26,7 +26,7 @@ rustls = { version = "0.23", default-features = false, features = ["ring"] } rustls-pemfile = "2.2" pki-types = { package = "rustls-pki-types", version = "1.10" } tokio-rustls = { version = "0.26", default-features = false } - +uuid = { version = "1.12.1", features = ["v4"] } [build-dependencies] cxx-build = "1.0" diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index 3a33aab91ee2..1663e849951f 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -26,6 +26,11 @@ #include "rust/cxx.h" #include "credentials.hh" +namespace Logr +{ +class Logger; +} + namespace pdns::rust::settings::rec { uint16_t qTypeStringToCode(::rust::Str str); @@ -36,6 +41,7 @@ void setThreadName(::rust::Str str); class NetmaskGroup; union ComboAddress; + namespace pdns::rust::web::rec { using CredentialsHolder = ::CredentialsHolder; @@ -44,6 +50,7 @@ struct KeyValue; struct Request; struct Response; struct IncomingWSConfig; +enum class Priority : uint8_t; template class Wrapper @@ -65,7 +72,8 @@ private: }; using NetmaskGroup = Wrapper<::NetmaskGroup>; using ComboAddress = Wrapper<::ComboAddress>; - +using Logger = Wrapper>; + void apiServer(const Request& rustRequest, Response& rustResponse); void apiDiscovery(const Request& rustRequest, Response& rustResponse); void apiDiscoveryV1(const Request& rustRequest, Response& rustResponse); @@ -89,4 +97,7 @@ void apiServerZoneDetailPUT(const Request& rustRequest, Response& rustResponse); void apiServerZoneDetailDELETE(const Request& rustRequest, Response& rustResponse); std::unique_ptr comboaddress(::rust::Str str); bool matches(const std::unique_ptr& nmg, const std::unique_ptr& address); +std::unique_ptr withValue(const std::unique_ptr& logger, ::rust::Str key, ::rust::Str val); +void log(const std::unique_ptr& logger, Priority log_level, ::rust::Str msg, const ::rust::Vec& values); + void error(const std::unique_ptr& logger, Priority log_level, ::rust::Str err, ::rust::Str msg, const ::rust::Vec& values); } diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index e0a1852f6a49..8453cb7c4c16 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -171,6 +171,7 @@ struct Context { password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr, + logger: cxx::UniquePtr, counter: Mutex, } @@ -183,7 +184,11 @@ fn file(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, } let pos = ctx.urls.iter().position(|x| String::from("/") + x == uripath); if pos.is_none() { - eprintln!("{} {} not found", method, uripath); + rustweb::log(&request.logger, rustweb::Priority::Debug, "not found", + &vec!( + rustweb::KeyValue{key: "method".to_string(), value: method.to_string()}, + rustweb::KeyValue{key: "uripath".to_string(), value: uripath.to_string()} + )); } // This calls into C++ @@ -191,7 +196,11 @@ fn file(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, // Return 404 not found response. response.status = StatusCode::NOT_FOUND.as_u16(); response.body = NOTFOUND.to_vec(); - eprintln!("{} {} not found case 2", method, uripath); + rustweb::log(&request.logger, rustweb::Priority::Debug, "not found case 2", + &vec!( + rustweb::KeyValue{key: "method".to_string(), value: method.to_string()}, + rustweb::KeyValue{key: "uripath".to_string(), value: uripath.to_string()} + )); } } @@ -260,7 +269,7 @@ fn matcher(method: &Method, path: &str, apifunc: &mut Option, rawfunc: &mu } // This constructs the answer to an OPTIONS query -fn collect_options(path: &str, response: &mut rustweb::Response) +fn collect_options(path: &str, response: &mut rustweb::Response, my_logger: &cxx::UniquePtr) { let mut methods = vec!(); for method in [Method::GET, Method::POST, Method::PUT, Method::DELETE] { @@ -273,6 +282,7 @@ fn collect_options(path: &str, response: &mut rustweb::Response) uri: String::from(""), vars: vec![], parameters: vec![], + logger: my_logger, }; matcher(&method, path, &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request); if apifunc.is_some() || rawfunc.is_some() /* || filefunc.is_some() */ { @@ -292,16 +302,29 @@ fn collect_options(path: &str, response: &mut rustweb::Response) response.headers.push(rustweb::KeyValue{key: String::from("content-type"), value: String::from("text/plain")}); } +fn log_request(request: &rustweb::Request, remote: SocketAddr) +{ +} + +fn log_response(response: &rustweb::Response, remote: SocketAddr) +{ +} + // Main entry point after a request arrived async fn process_request( rust_request: Request, - ctx: Arc + ctx: Arc, + remote: SocketAddr, ) -> MyResult> { { // For demo purposes let mut counter = ctx.counter.lock().await; *counter += 1; } + + let unique = uuid::Uuid::new_v4(); + let my_logger = rustweb::withValue(&ctx.logger, "uniqueid", &unique.to_string()); + // Convert query part of URI into vars table let mut vars: Vec = vec![]; if let Some(query) = rust_request.uri().query() { @@ -324,7 +347,11 @@ async fn process_request( uri: rust_request.uri().to_string(), vars, parameters: vec![], + logger: &my_logger, }; + + log_request(&request, remote); + let mut response = rustweb::Response { status: 0, body: vec![], @@ -337,12 +364,15 @@ async fn process_request( let mut allow_password = false; let mut rust_response = Response::builder(); + let path = rust_request.uri().path().to_owned(); + let version = rust_request.version().to_owned(); + if method == Method::OPTIONS { - collect_options(rust_request.uri().path(), &mut response); + collect_options(&path, &mut response, &my_logger); } else { // Find the right fucntion implementing what the request wants - matcher(&method, rust_request.uri().path(), &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request); + matcher(&method, &path, &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request); if let Some(func) = apifunc { let reqheaders = rust_request.headers().clone(); @@ -357,7 +387,7 @@ async fn process_request( &mut response, &reqheaders, rust_response.headers_mut().expect("no headers?"), - allow_password, + allow_password ); } else if let Some(func) = rawfunc { @@ -373,6 +403,19 @@ async fn process_request( func(&ctx, &method, rust_request.uri().path(), &request, &mut response); } } + + log_response(&response, remote); + if true { // XXX + let version = format!("{:?}", version); + rustweb::log(&my_logger, rustweb::Priority::Notice, "Request", &vec!( + rustweb::KeyValue{key: "remote".to_string(), value: remote.to_string()}, + rustweb::KeyValue{key: "method".to_string(), value: method.to_string()}, + rustweb::KeyValue{key: "urlpath".to_string(), value: path.to_string()}, + rustweb::KeyValue{key: "HTTPVersion".to_string(), value: version}, + rustweb::KeyValue{key: "status".to_string(), value: response.status.to_string()}, + rustweb::KeyValue{key: "respsize".to_string(), value: response.body.len().to_string()}, + )); + } // Throw away body for HEAD call let mut body = full(response.body); if method == Method::HEAD { @@ -394,6 +437,7 @@ async fn process_request( header::CONNECTION, header::HeaderValue::from_str("close").unwrap(), ); + Ok(rust_response) } @@ -413,17 +457,18 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco let ctx = Arc::clone(&ctx); let (stream, _) = listener.accept().await?; + let address; match stream.peer_addr() { - Ok(address) => { - eprintln!("Peer: {:?}", address); + Ok(addr) => { + address = addr; let combo = rustweb::comboaddress(&address.to_string()); if !rustweb::matches(&ctx.acl, &combo) { - eprintln!("No acl match! {:?}", address); + rustweb::log(&ctx.logger, rustweb::Priority::Debug, "No ACL match", &vec!(rustweb::KeyValue{key: "address".to_string(), value: address.to_string()})); continue; } } Err(err) => { - eprintln!("Can't get: {:?}", err); + rustweb::error(&ctx.logger, rustweb::Priority::Error, &err.to_string(), "Can't get peer address", &vec!()); continue; // If we can't determine the peer address, don't } } @@ -433,7 +478,7 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco let tls_stream = match tls_acceptor.accept(stream).await { Ok(tls_stream) => tls_stream, Err(err) => { - eprintln!("failed to perform tls handshake: {err:#}"); + rustweb::error(&ctx.logger, rustweb::Priority::Notice, &err.to_string(), "Failed to perform TLS handshake", &vec!()); continue; } }; @@ -441,14 +486,14 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco let fut = http1::Builder::new().serve_connection(io, service_fn(move |req| { let ctx = Arc::clone(&ctx); - process_request(req, ctx) + process_request(req, ctx, address) })); // Spawn a tokio task to serve the request tokio::task::spawn(async move { // Finally, we bind the incoming connection to our `process_request` service if let Err(err) = fut.await { - eprintln!("Error serving connection: {:?}", err); + //rustweb::error(&ctx.logger, rustweb::Priority::Notice, &err.to_string(), "Error serving web connection", &vec!()); } }); } @@ -459,17 +504,18 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco let ctx = Arc::clone(&ctx); let (stream, _) = listener.accept().await?; + let address; match stream.peer_addr() { - Ok(address) => { - eprintln!("Peer: {:?}", address); + Ok(addr) => { + address = addr; let combo = rustweb::comboaddress(&address.to_string()); if !rustweb::matches(&ctx.acl, &combo) { - eprintln!("No acl match! {:?}", address); + rustweb::log(&ctx.logger, rustweb::Priority::Debug, "No ACL match", &vec!(rustweb::KeyValue{key: "address".to_string(), value: address.to_string()})); continue; } } Err(err) => { - eprintln!("Can't get: {:?}", err); + rustweb::error(&ctx.logger, rustweb::Priority::Error, &err.to_string(), "Can't get peer address", &vec!()); continue; // If we can't determine the peer address, don't } } @@ -477,28 +523,29 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco let fut = http1::Builder::new().serve_connection(io, service_fn(move |req| { let ctx = Arc::clone(&ctx); - process_request(req, ctx) + process_request(req, ctx, address) })); // Spawn a tokio task to serve the request tokio::task::spawn(async move { // Finally, we bind the incoming connection to our `process_request` service if let Err(err) = fut.await { - eprintln!("Error serving connection: {:?}", err); - } + //rustweb::error(&ctx.logger, rustweb::Priority::Notice, &err.to_string(), "Error serving web connection", &vec!()); + } }); } } } -pub fn serveweb(incoming: &Vec, urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr) -> Result<(), std::io::Error> { - println!("SERVEWEB"); +pub fn serveweb(incoming: &Vec, urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr, logger: cxx::UniquePtr) -> Result<(), std::io::Error> { + // Context, atomically reference counted let ctx = Arc::new(Context { urls: urls.to_vec(), password_ch, api_ch, acl, + logger, counter: Mutex::new(0), // more for educational purposes }); @@ -513,9 +560,8 @@ pub fn serveweb(incoming: &Vec, urls: &[String], pass // waits (forever) for all of them to complete by joining them all. let mut set = JoinSet::new(); for config in incoming { - println!("Config"); + rustweb::log(&ctx.logger, rustweb::Priority::Warning, "Config", &vec!()); for addr_str in &config.addresses { - println!("Config Addr {}", addr_str); let addr = match SocketAddr::from_str(addr_str) { Ok(val) => val, Err(err) => { @@ -528,16 +574,26 @@ pub fn serveweb(incoming: &Vec, urls: &[String], pass let ctx = Arc::clone(&ctx); match listener { Ok(val) => { + let mut tls_enabled = false; let tls = crate::web::rustweb::IncomingTLS { certificate: config.tls.certificate.clone(), key: config.tls.key.clone(), password: config.tls.password.clone(), }; - println!("Listening on {}", addr); + if !tls.certificate.is_empty() { + tls_enabled = true; + } + rustweb::log(&ctx.logger, rustweb::Priority::Info, "web service listening", + &vec!(rustweb::KeyValue{key: "address".to_string(), value: addr.to_string()}, + rustweb::KeyValue{key: "tls".to_string(), value: tls_enabled.to_string()} + ) + ); set.spawn_on(serveweb_async(val, tls, ctx), runtime.handle()); } Err(err) => { let msg = format!("Unable to bind web socket: {}", err); + rustweb::error(&ctx.logger, rustweb::Priority::Error, &err.to_string(), "Unable to bind to web socket", + &vec!(rustweb::KeyValue{key: "address".to_string(), value: addr.to_string()})); return Err(std::io::Error::new(ErrorKind::Other, msg)); } } @@ -548,7 +604,8 @@ pub fn serveweb(incoming: &Vec, urls: &[String], pass .spawn(move || { runtime.block_on(async { while let Some(res) = set.join_next().await { - println!("{:?}", res); + let msg = format!("{:?}", res); + rustweb::error(&ctx.logger, rustweb::Priority::Error, &msg, "rustweb thread exited", &vec!()); } }); })?; @@ -582,6 +639,8 @@ unsafe impl Send for rustweb::CredentialsHolder {} unsafe impl Sync for rustweb::CredentialsHolder {} unsafe impl Send for rustweb::NetmaskGroup {} unsafe impl Sync for rustweb::NetmaskGroup {} +unsafe impl Send for rustweb::Logger {} +unsafe impl Sync for rustweb::Logger {} #[cxx::bridge(namespace = "pdns::rust::web::rec")] mod rustweb { @@ -589,6 +648,7 @@ mod rustweb { type CredentialsHolder; type NetmaskGroup; type ComboAddress; + type Logger; } pub struct IncomingTLS { @@ -606,7 +666,7 @@ mod rustweb { */ extern "Rust" { // The main entry point, This function will return, but will setup thread(s) to handle requests. - fn serveweb(incoming: &Vec, urls: &[String], pwch: UniquePtr, apikeych: UniquePtr, acl: UniquePtr) -> Result<()>; + fn serveweb(incoming: &Vec, urls: &[String], pwch: UniquePtr, apikeych: UniquePtr, acl: UniquePtr, logger: UniquePtr) -> Result<()>; } struct KeyValue { @@ -614,11 +674,12 @@ mod rustweb { value: String, } - struct Request { + struct Request<'a> { body: Vec, uri: String, vars: Vec, parameters: Vec, + logger: &'a UniquePtr, } struct Response { @@ -627,6 +688,17 @@ mod rustweb { headers: Vec, } + enum Priority { + Absent = 0, + Alert = 1, + Critical = 2, + Error = 3, + Warning = 4, + Notice = 5, + Info = 6, + Debug = 7 + } + /* * Functions callable from Rust */ @@ -657,5 +729,8 @@ mod rustweb { fn matches(self: &CredentialsHolder, str: &CxxString) -> bool; fn comboaddress(address: &str) -> UniquePtr; fn matches(nmg: &UniquePtr, address: &UniquePtr) -> bool; // match is a keyword + fn withValue(logger: &UniquePtr, key: &str, val: &str) -> UniquePtr; + fn log(logger: &UniquePtr, prio: Priority, msg: &str, values: &Vec); + fn error(logger: &UniquePtr, prio: Priority, err: &str, msg: &str, values: &Vec); } } diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 1bd995ad6758..8c84e3ddc2d6 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -1024,8 +1024,10 @@ void serveRustWeb() acl.toMasks(::arg()["webserver-allow-from"]); auto aclPtr = std::make_unique(acl); - cerr << "CALL SERVEWEB" << endl; - pdns::rust::web::rec::serveweb(config, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr)); + cerr << "CALL SERVEWEB" << endl; + auto logPtr = std::make_unique(g_slog->withName("webserver")); + + pdns::rust::web::rec::serveweb(config, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr), std::move(logPtr)); } static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse) From cf1902d70122b5fa43baf865acb657b1a523751e Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Mon, 27 Jan 2025 15:41:20 +0100 Subject: [PATCH 20/38] Fix HEAD --- pdns/recursordist/settings/rust/src/web.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 8453cb7c4c16..67c180543db5 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -371,8 +371,12 @@ async fn process_request( collect_options(&path, &mut response, &my_logger); } else { - // Find the right fucntion implementing what the request wants - matcher(&method, &path, &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request); + // Find the right function implementing what the request wants + let mut matchmethod = method.clone(); + if method == Method::HEAD { + matchmethod = Method::GET; + } + matcher(&matchmethod, &path, &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request); if let Some(func) = apifunc { let reqheaders = rust_request.headers().clone(); @@ -404,6 +408,10 @@ async fn process_request( } } + let mut len = response.body.len(); + if method == Method::HEAD { + len = 0; + } log_response(&response, remote); if true { // XXX let version = format!("{:?}", version); @@ -413,7 +421,7 @@ async fn process_request( rustweb::KeyValue{key: "urlpath".to_string(), value: path.to_string()}, rustweb::KeyValue{key: "HTTPVersion".to_string(), value: version}, rustweb::KeyValue{key: "status".to_string(), value: response.status.to_string()}, - rustweb::KeyValue{key: "respsize".to_string(), value: response.body.len().to_string()}, + rustweb::KeyValue{key: "respsize".to_string(), value: len.to_string()}, )); } // Throw away body for HEAD call From f5b7b8d09edc7f4e496302dcf5895b34784c44fd Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Mon, 27 Jan 2025 16:18:28 +0100 Subject: [PATCH 21/38] Log getvars --- pdns/recursordist/settings/rust/src/web.rs | 113 +++++++++++++++------ pdns/recursordist/ws-recursor.cc | 13 ++- 2 files changed, 92 insertions(+), 34 deletions(-) diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 67c180543db5..d4d7f95d1fdb 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -28,7 +28,6 @@ use tokio::task::JoinSet; use std::io::ErrorKind; use std::str::FromStr; use std::sync::Arc; -use tokio::sync::Mutex; use base64::prelude::*; type GenericError = Box; @@ -73,7 +72,6 @@ fn compare_authorization(ctx: &Context, reqheaders: &header::HeaderMap) -> bool fn unauthorized(response: &mut rustweb::Response, headers: &mut header::HeaderMap, auth: &str) { - // XXX log let status = StatusCode::UNAUTHORIZED; response.status = status.as_u16(); let val = format!("{} realm=\"PowerDNS\"", auth); @@ -85,6 +83,7 @@ fn unauthorized(response: &mut rustweb::Response, headers: &mut header::HeaderMa } fn api_wrapper( + logger: &cxx::UniquePtr, ctx: &Context, handler: Func, request: &rustweb::Request, @@ -100,6 +99,8 @@ fn api_wrapper( header::HeaderValue::from_static("*"), ); if ctx.api_ch.is_null() { + rustweb::log(logger, rustweb::Priority::Error, "Authentication failed, API Key missing in config", + &vec!(rustweb::KeyValue{key: "urlpath".to_string(), value: request.uri.to_owned()})); unauthorized(response, headers, "X-API-Key"); return; } @@ -128,6 +129,8 @@ fn api_wrapper( } } if !auth_ok { + rustweb::log(logger, rustweb::Priority::Error, "Authentication failed", + &vec!(rustweb::KeyValue{key: "urlpath".to_string(), value: request.uri.to_owned()})); unauthorized(response, headers, "X-API-Key"); return; } @@ -172,7 +175,7 @@ struct Context { api_ch: cxx::UniquePtr, acl: cxx::UniquePtr, logger: cxx::UniquePtr, - counter: Mutex, + loglevel: rustweb::LogLevel, } // Serve a file @@ -302,12 +305,60 @@ fn collect_options(path: &str, response: &mut rustweb::Response, my_logger: &cxx response.headers.push(rustweb::KeyValue{key: String::from("content-type"), value: String::from("text/plain")}); } -fn log_request(request: &rustweb::Request, remote: SocketAddr) +fn log_request(loglevel: rustweb::LogLevel, request: &rustweb::Request, remote: SocketAddr) { + if loglevel != rustweb::LogLevel::Detailed { + return; + } + let body; + match std::str::from_utf8(&request.body) { + Ok(cvt) => body = cvt, + Err(_) => body = "error: body is not utf8" + } + let mut vec = vec!( + rustweb::KeyValue{key: "remote".to_string(), value: remote.to_string()}, + rustweb::KeyValue{key: "body".to_string(), value: body.to_string()} + ); + let mut first = true; + let mut str = "".to_string(); + for var in &request.vars { + if !first { + str.push_str(" "); + } + first = false; + let snippet = var.key.to_owned() + "=" + &var.value; + str.push_str(&snippet); + } + vec.push(rustweb::KeyValue{key: "getVars".to_string(), value: str.to_string()}); + rustweb::log(&request.logger, rustweb::Priority::Info, "Request details", &vec); } -fn log_response(response: &rustweb::Response, remote: SocketAddr) +fn log_response(loglevel: rustweb::LogLevel, logger: &cxx::UniquePtr, response: &rustweb::Response, remote: SocketAddr) { + if loglevel != rustweb::LogLevel::Detailed { + return; + } + let body; + match std::str::from_utf8(&response.body) { + Ok(cvt) => body = cvt, + Err(_) => body = "error: body is not utf8" + } + let mut vec = vec!( + rustweb::KeyValue{key: "remote".to_string(), value: remote.to_string()}, + rustweb::KeyValue{key: "body".to_string(), value: body.to_string()} + ); + let mut first = true; + let mut str = "".to_string(); + for var in &response.headers { + if !first { + str.push_str(" "); + } + first = false; + let snippet = var.key.to_owned() + "=" + &var.value; + str.push_str(&snippet); + } + vec.push(rustweb::KeyValue{key: "headers".to_string(), value: str.to_string()}); + rustweb::log(logger, rustweb::Priority::Info, "Response details", &vec); } // Main entry point after a request arrived @@ -316,11 +367,6 @@ async fn process_request( ctx: Arc, remote: SocketAddr, ) -> MyResult> { - { - // For demo purposes - let mut counter = ctx.counter.lock().await; - *counter += 1; - } let unique = uuid::Uuid::new_v4(); let my_logger = rustweb::withValue(&ctx.logger, "uniqueid", &unique.to_string()); @@ -350,7 +396,7 @@ async fn process_request( logger: &my_logger, }; - log_request(&request, remote); + log_request(ctx.loglevel, &request, remote); let mut response = rustweb::Response { status: 0, @@ -366,7 +412,7 @@ async fn process_request( let path = rust_request.uri().path().to_owned(); let version = rust_request.version().to_owned(); - + if method == Method::OPTIONS { collect_options(&path, &mut response, &my_logger); } @@ -385,6 +431,7 @@ async fn process_request( } // This calls indirectly into C++ api_wrapper( + &my_logger, &ctx, func, &request, @@ -412,18 +459,7 @@ async fn process_request( if method == Method::HEAD { len = 0; } - log_response(&response, remote); - if true { // XXX - let version = format!("{:?}", version); - rustweb::log(&my_logger, rustweb::Priority::Notice, "Request", &vec!( - rustweb::KeyValue{key: "remote".to_string(), value: remote.to_string()}, - rustweb::KeyValue{key: "method".to_string(), value: method.to_string()}, - rustweb::KeyValue{key: "urlpath".to_string(), value: path.to_string()}, - rustweb::KeyValue{key: "HTTPVersion".to_string(), value: version}, - rustweb::KeyValue{key: "status".to_string(), value: response.status.to_string()}, - rustweb::KeyValue{key: "respsize".to_string(), value: len.to_string()}, - )); - } + log_response(ctx.loglevel, &my_logger, &response, remote); // Throw away body for HEAD call let mut body = full(response.body); if method == Method::HEAD { @@ -445,6 +481,17 @@ async fn process_request( header::CONNECTION, header::HeaderValue::from_str("close").unwrap(), ); + if ctx.loglevel != rustweb::LogLevel::None { + let version = format!("{:?}", version); + rustweb::log(&my_logger, rustweb::Priority::Notice, "Request", &vec!( + rustweb::KeyValue{key: "remote".to_string(), value: remote.to_string()}, + rustweb::KeyValue{key: "method".to_string(), value: method.to_string()}, + rustweb::KeyValue{key: "urlpath".to_string(), value: path.to_string()}, + rustweb::KeyValue{key: "HTTPVersion".to_string(), value: version}, + rustweb::KeyValue{key: "status".to_string(), value: response.status.to_string()}, + rustweb::KeyValue{key: "respsize".to_string(), value: len.to_string()}, + )); + } Ok(rust_response) } @@ -491,6 +538,7 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco } }; let io = TokioIo::new(tls_stream); + let my_logger = rustweb::withValue(&ctx.logger, "tls", "true"); let fut = http1::Builder::new().serve_connection(io, service_fn(move |req| { let ctx = Arc::clone(&ctx); @@ -501,7 +549,7 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco tokio::task::spawn(async move { // Finally, we bind the incoming connection to our `process_request` service if let Err(err) = fut.await { - //rustweb::error(&ctx.logger, rustweb::Priority::Notice, &err.to_string(), "Error serving web connection", &vec!()); + rustweb::error(&my_logger, rustweb::Priority::Notice, &err.to_string(), "Error serving web connection", &vec!()); } }); } @@ -528,6 +576,7 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco } } let io = TokioIo::new(stream); + let my_logger = rustweb::withValue(&ctx.logger, "tls", "false"); let fut = http1::Builder::new().serve_connection(io, service_fn(move |req| { let ctx = Arc::clone(&ctx); @@ -538,14 +587,14 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco tokio::task::spawn(async move { // Finally, we bind the incoming connection to our `process_request` service if let Err(err) = fut.await { - //rustweb::error(&ctx.logger, rustweb::Priority::Notice, &err.to_string(), "Error serving web connection", &vec!()); + rustweb::error(&my_logger, rustweb::Priority::Notice, &err.to_string(), "Error serving web connection", &vec!()); } }); } } } -pub fn serveweb(incoming: &Vec, urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr, logger: cxx::UniquePtr) -> Result<(), std::io::Error> { +pub fn serveweb(incoming: &Vec, urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr, logger: cxx::UniquePtr, loglevel: rustweb::LogLevel) -> Result<(), std::io::Error> { // Context, atomically reference counted let ctx = Arc::new(Context { @@ -554,7 +603,7 @@ pub fn serveweb(incoming: &Vec, urls: &[String], pass api_ch, acl, logger, - counter: Mutex::new(0), // more for educational purposes + loglevel, }); // We use a single thread to handle all the requests, letting the runtime abstracts from this @@ -674,7 +723,7 @@ mod rustweb { */ extern "Rust" { // The main entry point, This function will return, but will setup thread(s) to handle requests. - fn serveweb(incoming: &Vec, urls: &[String], pwch: UniquePtr, apikeych: UniquePtr, acl: UniquePtr, logger: UniquePtr) -> Result<()>; + fn serveweb(incoming: &Vec, urls: &[String], pwch: UniquePtr, apikeych: UniquePtr, acl: UniquePtr, logger: UniquePtr, loglevel: LogLevel) -> Result<()>; } struct KeyValue { @@ -706,7 +755,11 @@ mod rustweb { Info = 6, Debug = 7 } - + enum LogLevel { + None, + Normal, + Detailed + } /* * Functions callable from Rust */ diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 8c84e3ddc2d6..9e2e1e515eb8 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -985,7 +985,6 @@ void AsyncWebServer::go() void serveRustWeb() { - cerr << "SERVERUSTWEB" << endl; ::rust::Vec config; if (g_yamlSettings) { @@ -999,7 +998,6 @@ void serveRustWeb() config.emplace_back(tmp); } } - cerr << "Config ahs " << config.size() << " entries" << endl; if (config.empty()) { auto address = ComboAddress(arg()["webserver-address"], arg().asNum("webserver-port")); pdns::rust::web::rec::IncomingWSConfig tmp{{::rust::String{address.toStringWithPort()}}, {}}; @@ -1024,10 +1022,17 @@ void serveRustWeb() acl.toMasks(::arg()["webserver-allow-from"]); auto aclPtr = std::make_unique(acl); - cerr << "CALL SERVEWEB" << endl; auto logPtr = std::make_unique(g_slog->withName("webserver")); - pdns::rust::web::rec::serveweb(config, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr), std::move(logPtr)); + pdns::rust::web::rec::LogLevel loglevel = pdns::rust::web::rec::LogLevel::Normal; + auto configLevel = ::arg()["webserver-loglevel"]; + if (configLevel == "none") { + loglevel = pdns::rust::web::rec::LogLevel::Normal; + } + else if (configLevel == "detailed") { + loglevel = pdns::rust::web::rec::LogLevel::Detailed; + } + pdns::rust::web::rec::serveweb(config, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr), std::move(logPtr), loglevel); } static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse) From f12615ea0706522e86a6c2ef6c7c7e2adb77cf99 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Tue, 28 Jan 2025 13:01:53 +0100 Subject: [PATCH 22/38] Wrap non-api and file calls to check for Basic auth --- pdns/recursordist/settings/rust/src/web.rs | 51 ++++++++++++++++++---- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index d4d7f95d1fdb..081a82f01319 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -1,9 +1,7 @@ /* TODO - Table based routing? -- Logging - Authorization: metrics and plain files (and more?) are not subject to password auth plus the code needs a n careful audit. -- TLS? - Code is now in settings dir. It's only possible to split the modules into separate Rust libs if we use shared libs (in theory, I did not try). Currently all CXX using Rust cargo's must be compiled as one and refer to a single static Rust runtime @@ -82,6 +80,43 @@ fn unauthorized(response: &mut rustweb::Response, headers: &mut header::HeaderMa response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); } +fn nonapi_wrapper( + ctx: &Context, + handler: Func, + request: &rustweb::Request, + response: &mut rustweb::Response, + reqheaders: &header::HeaderMap, + headers: &mut header::HeaderMap +) { + let auth_ok = compare_authorization(ctx, reqheaders); + if !auth_ok { + rustweb::log(request.logger, rustweb::Priority::Debug, "Authentication failed", + &vec!(rustweb::KeyValue{key: "urlpath".to_string(), value: request.uri.to_owned()})); + unauthorized(response, headers, "Basic"); + return; + } + match handler(request, response) { + Ok(_) => {} + Err(_) => { + let status = StatusCode::UNPROCESSABLE_ENTITY; // 422 + response.status = status.as_u16(); + response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); + } + } +} + +fn file_wrapper(ctx: &Context, handler: FileFunc, method: &Method, path: &str, request: &rustweb::Request, response: &mut rustweb::Response, reqheaders: &header::HeaderMap, headers: &mut header::HeaderMap) +{ + let auth_ok = compare_authorization(ctx, reqheaders); + if !auth_ok { + rustweb::log(request.logger, rustweb::Priority::Debug, "Authentication failed", + &vec!(rustweb::KeyValue{key: "urlpath".to_string(), value: request.uri.to_owned()})); + unauthorized(response, headers, "Basic"); + return; + } + handler(ctx, method, path, request, response); +} + fn api_wrapper( logger: &cxx::UniquePtr, ctx: &Context, @@ -124,6 +159,8 @@ fn api_wrapper( if !auth_ok && allow_password { auth_ok = compare_authorization(ctx, reqheaders); if !auth_ok { + rustweb::log(logger, rustweb::Priority::Debug, "Authentication failed", + &vec!(rustweb::KeyValue{key: "urlpath".to_string(), value: request.uri.to_owned()})); unauthorized(response, headers, "Basic"); return; } @@ -443,15 +480,13 @@ async fn process_request( } else if let Some(func) = rawfunc { // Non-API func - if func(&request, &mut response).is_err() { - let status = StatusCode::UNPROCESSABLE_ENTITY; // 422 - response.status = status.as_u16(); - response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); - } + let reqheaders = rust_request.headers().clone(); + nonapi_wrapper(&ctx, func, &request, &mut response, &reqheaders, rust_response.headers_mut().expect("no headers?")); } else if let Some(func) = filefunc { // Server static file - func(&ctx, &method, rust_request.uri().path(), &request, &mut response); + let reqheaders = rust_request.headers().clone(); + file_wrapper(&ctx, func, &method, rust_request.uri().path(), &request, &mut response, &reqheaders, rust_response.headers_mut().expect("no headers?")); } } From 8e2f11358160287241f04cc4feb8bcfa871c9a74 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Tue, 28 Jan 2025 13:11:58 +0100 Subject: [PATCH 23/38] Advice fomr clippy --- pdns/recursordist/settings/cxxsupport.cc | 2 +- pdns/recursordist/settings/rust/src/bridge.hh | 5 +- pdns/recursordist/settings/rust/src/bridge.rs | 13 +- pdns/recursordist/settings/rust/src/web.rs | 639 +++++++++++++----- pdns/recursordist/ws-recursor.cc | 1 - 5 files changed, 479 insertions(+), 181 deletions(-) diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/settings/cxxsupport.cc index 82dd3c8451af..6ef3378bf747 100644 --- a/pdns/recursordist/settings/cxxsupport.cc +++ b/pdns/recursordist/settings/cxxsupport.cc @@ -1499,7 +1499,7 @@ void log(const std::unique_ptr& logger, pdns::rust::web::rec::Priority l log->info(static_cast(log_level), std::string(msg)); } - void error(const std::unique_ptr& logger, pdns::rust::web::rec::Priority log_level, ::rust::Str error, ::rust::Str msg, const ::rust::Vec& values) +void error(const std::unique_ptr& logger, pdns::rust::web::rec::Priority log_level, ::rust::Str error, ::rust::Str msg, const ::rust::Vec& values) { auto log = logger->get(); for (const auto& [key, value] : values) { diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index 1663e849951f..b23d74872a5d 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -41,7 +41,6 @@ void setThreadName(::rust::Str str); class NetmaskGroup; union ComboAddress; - namespace pdns::rust::web::rec { using CredentialsHolder = ::CredentialsHolder; @@ -73,7 +72,7 @@ private: using NetmaskGroup = Wrapper<::NetmaskGroup>; using ComboAddress = Wrapper<::ComboAddress>; using Logger = Wrapper>; - + void apiServer(const Request& rustRequest, Response& rustResponse); void apiDiscovery(const Request& rustRequest, Response& rustResponse); void apiDiscoveryV1(const Request& rustRequest, Response& rustResponse); @@ -99,5 +98,5 @@ std::unique_ptr comboaddress(::rust::Str str); bool matches(const std::unique_ptr& nmg, const std::unique_ptr& address); std::unique_ptr withValue(const std::unique_ptr& logger, ::rust::Str key, ::rust::Str val); void log(const std::unique_ptr& logger, Priority log_level, ::rust::Str msg, const ::rust::Vec& values); - void error(const std::unique_ptr& logger, Priority log_level, ::rust::Str err, ::rust::Str msg, const ::rust::Vec& values); +void error(const std::unique_ptr& logger, Priority log_level, ::rust::Str err, ::rust::Str msg, const ::rust::Vec& values); } diff --git a/pdns/recursordist/settings/rust/src/bridge.rs b/pdns/recursordist/settings/rust/src/bridge.rs index 752b8830c850..e67931d464c6 100644 --- a/pdns/recursordist/settings/rust/src/bridge.rs +++ b/pdns/recursordist/settings/rust/src/bridge.rs @@ -217,7 +217,8 @@ fn validate_address_family( let sa = SocketAddr::from_str(addr_str); if sa.is_err() { let ip = IpAddr::from_str(addr_str); - if ip.is_err() { // It is likely a name + if ip.is_err() { + // It is likely a name continue; } let ip = ip.unwrap(); @@ -704,9 +705,13 @@ impl ForwardingCatalogZone { pub fn validate(&self, field: &str) -> Result<(), ValidationError> { self.xfr.tsig.validate(&(field.to_owned() + ".xfr.tsig"))?; if !self.xfr.addresses.is_empty() { - validate_address_family(&(field.to_owned() + ".xfr.addresses"), &(field.to_owned() + ".xfr.localAddress"), &self.xfr.addresses, &self.xfr.localAddress)?; - } - else { + validate_address_family( + &(field.to_owned() + ".xfr.addresses"), + &(field.to_owned() + ".xfr.localAddress"), + &self.xfr.addresses, + &self.xfr.localAddress, + )?; + } else { let msg = format!("{}.xfr.addresses: at least one address required", field); return Err(ValidationError { msg }); } diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 081a82f01319..876b5a22a985 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -14,19 +14,19 @@ TODO use std::net::SocketAddr; +use base64::prelude::*; use bytes::Bytes; use http_body_util::{BodyExt, Full}; use hyper::server::conn::http1; use hyper::service::service_fn; use hyper::{body::Incoming as IncomingBody, header, Method, Request, Response, StatusCode}; use hyper_util::rt::TokioIo; -use tokio::net::TcpListener; -use tokio::runtime::Builder; -use tokio::task::JoinSet; use std::io::ErrorKind; use std::str::FromStr; use std::sync::Arc; -use base64::prelude::*; +use tokio::net::TcpListener; +use tokio::runtime::Builder; +use tokio::task::JoinSet; type GenericError = Box; type MyResult = std::result::Result; @@ -42,8 +42,7 @@ fn full>(chunk: T) -> BoxBody { type Func = fn(&rustweb::Request, &mut rustweb::Response) -> Result<(), cxx::Exception>; -fn compare_authorization(ctx: &Context, reqheaders: &header::HeaderMap) -> bool -{ +fn compare_authorization(ctx: &Context, reqheaders: &header::HeaderMap) -> bool { let mut auth_ok = false; if !ctx.password_ch.is_null() { if let Some(authorization) = reqheaders.get("authorization") { @@ -68,9 +67,8 @@ fn compare_authorization(ctx: &Context, reqheaders: &header::HeaderMap) -> bool auth_ok } -fn unauthorized(response: &mut rustweb::Response, headers: &mut header::HeaderMap, auth: &str) -{ - let status = StatusCode::UNAUTHORIZED; +fn unauthorized(response: &mut rustweb::Response, headers: &mut header::HeaderMap, auth: &str) { + let status = StatusCode::UNAUTHORIZED; response.status = status.as_u16(); let val = format!("{} realm=\"PowerDNS\"", auth); headers.insert( @@ -86,31 +84,53 @@ fn nonapi_wrapper( request: &rustweb::Request, response: &mut rustweb::Response, reqheaders: &header::HeaderMap, - headers: &mut header::HeaderMap + headers: &mut header::HeaderMap, ) { let auth_ok = compare_authorization(ctx, reqheaders); if !auth_ok { - rustweb::log(request.logger, rustweb::Priority::Debug, "Authentication failed", - &vec!(rustweb::KeyValue{key: "urlpath".to_string(), value: request.uri.to_owned()})); + rustweb::log( + request.logger, + rustweb::Priority::Debug, + "Authentication failed", + &vec![rustweb::KeyValue { + key: "urlpath".to_string(), + value: request.uri.to_owned(), + }], + ); unauthorized(response, headers, "Basic"); return; } match handler(request, response) { Ok(_) => {} Err(_) => { - let status = StatusCode::UNPROCESSABLE_ENTITY; // 422 + let status = StatusCode::UNPROCESSABLE_ENTITY; // 422 response.status = status.as_u16(); response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); } } } -fn file_wrapper(ctx: &Context, handler: FileFunc, method: &Method, path: &str, request: &rustweb::Request, response: &mut rustweb::Response, reqheaders: &header::HeaderMap, headers: &mut header::HeaderMap) -{ +fn file_wrapper( + ctx: &Context, + handler: FileFunc, + method: &Method, + path: &str, + request: &rustweb::Request, + response: &mut rustweb::Response, + reqheaders: &header::HeaderMap, + headers: &mut header::HeaderMap, +) { let auth_ok = compare_authorization(ctx, reqheaders); if !auth_ok { - rustweb::log(request.logger, rustweb::Priority::Debug, "Authentication failed", - &vec!(rustweb::KeyValue{key: "urlpath".to_string(), value: request.uri.to_owned()})); + rustweb::log( + request.logger, + rustweb::Priority::Debug, + "Authentication failed", + &vec![rustweb::KeyValue { + key: "urlpath".to_string(), + value: request.uri.to_owned(), + }], + ); unauthorized(response, headers, "Basic"); return; } @@ -125,17 +145,23 @@ fn api_wrapper( response: &mut rustweb::Response, reqheaders: &header::HeaderMap, headers: &mut header::HeaderMap, - allow_password: bool + allow_password: bool, ) { - // security headers headers.insert( header::ACCESS_CONTROL_ALLOW_ORIGIN, header::HeaderValue::from_static("*"), ); if ctx.api_ch.is_null() { - rustweb::log(logger, rustweb::Priority::Error, "Authentication failed, API Key missing in config", - &vec!(rustweb::KeyValue{key: "urlpath".to_string(), value: request.uri.to_owned()})); + rustweb::log( + logger, + rustweb::Priority::Error, + "Authentication failed, API Key missing in config", + &vec![rustweb::KeyValue { + key: "urlpath".to_string(), + value: request.uri.to_owned(), + }], + ); unauthorized(response, headers, "X-API-Key"); return; } @@ -159,15 +185,29 @@ fn api_wrapper( if !auth_ok && allow_password { auth_ok = compare_authorization(ctx, reqheaders); if !auth_ok { - rustweb::log(logger, rustweb::Priority::Debug, "Authentication failed", - &vec!(rustweb::KeyValue{key: "urlpath".to_string(), value: request.uri.to_owned()})); + rustweb::log( + logger, + rustweb::Priority::Debug, + "Authentication failed", + &vec![rustweb::KeyValue { + key: "urlpath".to_string(), + value: request.uri.to_owned(), + }], + ); unauthorized(response, headers, "Basic"); return; } } if !auth_ok { - rustweb::log(logger, rustweb::Priority::Error, "Authentication failed", - &vec!(rustweb::KeyValue{key: "urlpath".to_string(), value: request.uri.to_owned()})); + rustweb::log( + logger, + rustweb::Priority::Error, + "Authentication failed", + &vec![rustweb::KeyValue { + key: "urlpath".to_string(), + value: request.uri.to_owned(), + }], + ); unauthorized(response, headers, "X-API-Key"); return; } @@ -198,7 +238,7 @@ fn api_wrapper( match handler(request, response) { Ok(_) => {} Err(_) => { - let status = StatusCode::UNPROCESSABLE_ENTITY; // 422 + let status = StatusCode::UNPROCESSABLE_ENTITY; // 422 response.status = status.as_u16(); response.body = status.canonical_reason().unwrap().as_bytes().to_vec(); } @@ -216,19 +256,37 @@ struct Context { } // Serve a file -fn file(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, response: &mut rustweb::Response) -{ +fn file( + ctx: &Context, + method: &Method, + path: &str, + request: &rustweb::Request, + response: &mut rustweb::Response, +) { let mut uripath = path; if uripath == "/" { uripath = "/index.html"; } - let pos = ctx.urls.iter().position(|x| String::from("/") + x == uripath); + let pos = ctx + .urls + .iter() + .position(|x| String::from("/") + x == uripath); if pos.is_none() { - rustweb::log(&request.logger, rustweb::Priority::Debug, "not found", - &vec!( - rustweb::KeyValue{key: "method".to_string(), value: method.to_string()}, - rustweb::KeyValue{key: "uripath".to_string(), value: uripath.to_string()} - )); + rustweb::log( + request.logger, + rustweb::Priority::Debug, + "not found", + &vec![ + rustweb::KeyValue { + key: "method".to_string(), + value: method.to_string(), + }, + rustweb::KeyValue { + key: "uripath".to_string(), + value: uripath.to_string(), + }, + ], + ); } // This calls into C++ @@ -236,82 +294,122 @@ fn file(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, // Return 404 not found response. response.status = StatusCode::NOT_FOUND.as_u16(); response.body = NOTFOUND.to_vec(); - rustweb::log(&request.logger, rustweb::Priority::Debug, "not found case 2", - &vec!( - rustweb::KeyValue{key: "method".to_string(), value: method.to_string()}, - rustweb::KeyValue{key: "uripath".to_string(), value: uripath.to_string()} - )); + rustweb::log( + request.logger, + rustweb::Priority::Debug, + "not found case 2", + &vec![ + rustweb::KeyValue { + key: "method".to_string(), + value: method.to_string(), + }, + rustweb::KeyValue { + key: "uripath".to_string(), + value: uripath.to_string(), + }, + ], + ); } } -type FileFunc = fn(ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, response: &mut rustweb::Response); +type FileFunc = fn( + ctx: &Context, + method: &Method, + path: &str, + request: &rustweb::Request, + response: &mut rustweb::Response, +); // Match a request and return the function that imlements it, this should probably be table based. -fn matcher(method: &Method, path: &str, apifunc: &mut Option, rawfunc: &mut Option, filefunc: &mut Option, allow_password: &mut bool, request: &mut rustweb::Request) -{ +fn matcher( + method: &Method, + path: &str, + apifunc: &mut Option, + rawfunc: &mut Option, + filefunc: &mut Option, + allow_password: &mut bool, + request: &mut rustweb::Request, +) { let path: Vec<_> = path.split('/').skip(1).collect(); match (method, &*path) { (&Method::GET, ["jsonstat"]) => { *allow_password = true; *apifunc = Some(rustweb::jsonstat); } - (&Method::PUT, ["api", "v1", "servers", "localhost", "cache", "flush"]) => - *apifunc = Some(rustweb::apiServerCacheFlush), - (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-from"]) => - *apifunc = Some(rustweb::apiServerConfigAllowFromPUT), - (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-from"]) => - *apifunc = Some(rustweb::apiServerConfigAllowFromGET), - (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) => - *apifunc = Some(rustweb::apiServerConfigAllowNotifyFromPUT), - (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) => - *apifunc = Some(rustweb::apiServerConfigAllowNotifyFromGET), - (&Method::GET, ["api", "v1", "servers", "localhost", "config"]) => - *apifunc = Some(rustweb::apiServerConfig), - (&Method::GET, ["api", "v1", "servers", "localhost", "rpzstatistics"]) => - *apifunc = Some(rustweb::apiServerRPZStats), - (&Method::GET, ["api", "v1", "servers", "localhost", "search-data"]) => - *apifunc = Some(rustweb::apiServerSearchData), + (&Method::PUT, ["api", "v1", "servers", "localhost", "cache", "flush"]) => { + *apifunc = Some(rustweb::apiServerCacheFlush) + } + (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-from"]) => { + *apifunc = Some(rustweb::apiServerConfigAllowFromPUT) + } + (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-from"]) => { + *apifunc = Some(rustweb::apiServerConfigAllowFromGET) + } + (&Method::PUT, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) => { + *apifunc = Some(rustweb::apiServerConfigAllowNotifyFromPUT) + } + (&Method::GET, ["api", "v1", "servers", "localhost", "config", "allow-notify-from"]) => { + *apifunc = Some(rustweb::apiServerConfigAllowNotifyFromGET) + } + (&Method::GET, ["api", "v1", "servers", "localhost", "config"]) => { + *apifunc = Some(rustweb::apiServerConfig) + } + (&Method::GET, ["api", "v1", "servers", "localhost", "rpzstatistics"]) => { + *apifunc = Some(rustweb::apiServerRPZStats) + } + (&Method::GET, ["api", "v1", "servers", "localhost", "search-data"]) => { + *apifunc = Some(rustweb::apiServerSearchData) + } (&Method::GET, ["api", "v1", "servers", "localhost", "zones", id]) => { - request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); + request.parameters.push(rustweb::KeyValue { + key: String::from("id"), + value: String::from(*id), + }); *apifunc = Some(rustweb::apiServerZoneDetailGET); } (&Method::PUT, ["api", "v1", "servers", "localhost", "zones", id]) => { - request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); + request.parameters.push(rustweb::KeyValue { + key: String::from("id"), + value: String::from(*id), + }); *apifunc = Some(rustweb::apiServerZoneDetailPUT); } (&Method::DELETE, ["api", "v1", "servers", "localhost", "zones", id]) => { - request.parameters.push(rustweb::KeyValue{key: String::from("id"), value: String::from(*id)}); + request.parameters.push(rustweb::KeyValue { + key: String::from("id"), + value: String::from(*id), + }); *apifunc = Some(rustweb::apiServerZoneDetailDELETE); } (&Method::GET, ["api", "v1", "servers", "localhost", "statistics"]) => { *allow_password = true; *apifunc = Some(rustweb::apiServerStatistics); } - (&Method::GET, ["api", "v1", "servers", "localhost", "zones"]) => - *apifunc = Some(rustweb::apiServerZonesGET), - (&Method::POST, ["api", "v1", "servers", "localhost", "zones"]) => - *apifunc = Some(rustweb::apiServerZonesPOST), + (&Method::GET, ["api", "v1", "servers", "localhost", "zones"]) => { + *apifunc = Some(rustweb::apiServerZonesGET) + } + (&Method::POST, ["api", "v1", "servers", "localhost", "zones"]) => { + *apifunc = Some(rustweb::apiServerZonesPOST) + } (&Method::GET, ["api", "v1", "servers", "localhost"]) => { *allow_password = true; *apifunc = Some(rustweb::apiServerDetail); } - (&Method::GET, ["api", "v1", "servers"]) => - *apifunc = Some(rustweb::apiServer), - (&Method::GET, ["api", "v1"]) => - *apifunc = Some(rustweb::apiDiscoveryV1), - (&Method::GET, ["api"]) => - *apifunc = Some(rustweb::apiDiscovery), - (&Method::GET, ["metrics"]) => - *rawfunc = Some(rustweb::prometheusMetrics), - _ => - *filefunc = Some(file), + (&Method::GET, ["api", "v1", "servers"]) => *apifunc = Some(rustweb::apiServer), + (&Method::GET, ["api", "v1"]) => *apifunc = Some(rustweb::apiDiscoveryV1), + (&Method::GET, ["api"]) => *apifunc = Some(rustweb::apiDiscovery), + (&Method::GET, ["metrics"]) => *rawfunc = Some(rustweb::prometheusMetrics), + _ => *filefunc = Some(file), } } // This constructs the answer to an OPTIONS query -fn collect_options(path: &str, response: &mut rustweb::Response, my_logger: &cxx::UniquePtr) -{ - let mut methods = vec!(); +fn collect_options( + path: &str, + response: &mut rustweb::Response, + my_logger: &cxx::UniquePtr, +) { + let mut methods = vec![]; for method in [Method::GET, Method::POST, Method::PUT, Method::DELETE] { let mut apifunc: Option = None; let mut rawfunc: Option<_> = None; @@ -324,8 +422,18 @@ fn collect_options(path: &str, response: &mut rustweb::Response, my_logger: &cxx parameters: vec![], logger: my_logger, }; - matcher(&method, path, &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request); - if apifunc.is_some() || rawfunc.is_some() /* || filefunc.is_some() */ { + matcher( + &method, + path, + &mut apifunc, + &mut rawfunc, + &mut filefunc, + &mut allow_password, + &mut request, + ); + if apifunc.is_some() || rawfunc.is_some() + /* || filefunc.is_some() */ + { methods.push(method.to_string()); } } @@ -335,66 +443,99 @@ fn collect_options(path: &str, response: &mut rustweb::Response, my_logger: &cxx } response.status = 200; methods.push(Method::OPTIONS.to_string()); - response.headers.push(rustweb::KeyValue{key: String::from("access-control-allow-origin"), value: String::from("*")}); - response.headers.push(rustweb::KeyValue{key: String::from("access-control-allow-headers"), value: String::from("Content-Type, X-API-Key")}); - response.headers.push(rustweb::KeyValue{key: String::from("access-control-max-age"), value: String::from("3600")}); - response.headers.push(rustweb::KeyValue{key: String::from("access-control-allow-methods"), value: methods.join(", ")}); - response.headers.push(rustweb::KeyValue{key: String::from("content-type"), value: String::from("text/plain")}); + response.headers.push(rustweb::KeyValue { + key: String::from("access-control-allow-origin"), + value: String::from("*"), + }); + response.headers.push(rustweb::KeyValue { + key: String::from("access-control-allow-headers"), + value: String::from("Content-Type, X-API-Key"), + }); + response.headers.push(rustweb::KeyValue { + key: String::from("access-control-max-age"), + value: String::from("3600"), + }); + response.headers.push(rustweb::KeyValue { + key: String::from("access-control-allow-methods"), + value: methods.join(", "), + }); + response.headers.push(rustweb::KeyValue { + key: String::from("content-type"), + value: String::from("text/plain"), + }); } -fn log_request(loglevel: rustweb::LogLevel, request: &rustweb::Request, remote: SocketAddr) -{ - if loglevel != rustweb::LogLevel::Detailed { +fn log_request(loglevel: rustweb::LogLevel, request: &rustweb::Request, remote: SocketAddr) { + if loglevel != rustweb::LogLevel::Detailed { return; } - let body; - match std::str::from_utf8(&request.body) { - Ok(cvt) => body = cvt, - Err(_) => body = "error: body is not utf8" - } - let mut vec = vec!( - rustweb::KeyValue{key: "remote".to_string(), value: remote.to_string()}, - rustweb::KeyValue{key: "body".to_string(), value: body.to_string()} - ); + let body = std::str::from_utf8(&request.body).unwrap_or("error: body is not utf8"); + let mut vec = vec![ + rustweb::KeyValue { + key: "remote".to_string(), + value: remote.to_string(), + }, + rustweb::KeyValue { + key: "body".to_string(), + value: body.to_string(), + }, + ]; let mut first = true; let mut str = "".to_string(); for var in &request.vars { if !first { - str.push_str(" "); + str.push(' '); } first = false; let snippet = var.key.to_owned() + "=" + &var.value; str.push_str(&snippet); } - vec.push(rustweb::KeyValue{key: "getVars".to_string(), value: str.to_string()}); - rustweb::log(&request.logger, rustweb::Priority::Info, "Request details", &vec); + vec.push(rustweb::KeyValue { + key: "getVars".to_string(), + value: str.to_string(), + }); + rustweb::log( + request.logger, + rustweb::Priority::Info, + "Request details", + &vec, + ); } -fn log_response(loglevel: rustweb::LogLevel, logger: &cxx::UniquePtr, response: &rustweb::Response, remote: SocketAddr) -{ +fn log_response( + loglevel: rustweb::LogLevel, + logger: &cxx::UniquePtr, + response: &rustweb::Response, + remote: SocketAddr, +) { if loglevel != rustweb::LogLevel::Detailed { return; } - let body; - match std::str::from_utf8(&response.body) { - Ok(cvt) => body = cvt, - Err(_) => body = "error: body is not utf8" - } - let mut vec = vec!( - rustweb::KeyValue{key: "remote".to_string(), value: remote.to_string()}, - rustweb::KeyValue{key: "body".to_string(), value: body.to_string()} - ); + let body = std::str::from_utf8(&response.body).unwrap_or("error: body is not utf8"); + let mut vec = vec![ + rustweb::KeyValue { + key: "remote".to_string(), + value: remote.to_string(), + }, + rustweb::KeyValue { + key: "body".to_string(), + value: body.to_string(), + }, + ]; let mut first = true; let mut str = "".to_string(); for var in &response.headers { if !first { - str.push_str(" "); + str.push(' '); } first = false; let snippet = var.key.to_owned() + "=" + &var.value; str.push_str(&snippet); } - vec.push(rustweb::KeyValue{key: "headers".to_string(), value: str.to_string()}); + vec.push(rustweb::KeyValue { + key: "headers".to_string(), + value: str.to_string(), + }); rustweb::log(logger, rustweb::Priority::Info, "Response details", &vec); } @@ -404,7 +545,6 @@ async fn process_request( ctx: Arc, remote: SocketAddr, ) -> MyResult> { - let unique = uuid::Uuid::new_v4(); let my_logger = rustweb::withValue(&ctx.logger, "uniqueid", &unique.to_string()); @@ -452,18 +592,25 @@ async fn process_request( if method == Method::OPTIONS { collect_options(&path, &mut response, &my_logger); - } - else { + } else { // Find the right function implementing what the request wants let mut matchmethod = method.clone(); if method == Method::HEAD { matchmethod = Method::GET; } - matcher(&matchmethod, &path, &mut apifunc, &mut rawfunc, &mut filefunc, &mut allow_password, &mut request); + matcher( + &matchmethod, + &path, + &mut apifunc, + &mut rawfunc, + &mut filefunc, + &mut allow_password, + &mut request, + ); if let Some(func) = apifunc { let reqheaders = rust_request.headers().clone(); - if rust_request.method()== Method::POST || rust_request.method() == Method::PUT { + if rust_request.method() == Method::POST || rust_request.method() == Method::PUT { request.body = rust_request.collect().await?.to_bytes().to_vec(); } // This calls indirectly into C++ @@ -475,18 +622,32 @@ async fn process_request( &mut response, &reqheaders, rust_response.headers_mut().expect("no headers?"), - allow_password + allow_password, ); - } - else if let Some(func) = rawfunc { + } else if let Some(func) = rawfunc { // Non-API func let reqheaders = rust_request.headers().clone(); - nonapi_wrapper(&ctx, func, &request, &mut response, &reqheaders, rust_response.headers_mut().expect("no headers?")); - } - else if let Some(func) = filefunc { + nonapi_wrapper( + &ctx, + func, + &request, + &mut response, + &reqheaders, + rust_response.headers_mut().expect("no headers?"), + ); + } else if let Some(func) = filefunc { // Server static file let reqheaders = rust_request.headers().clone(); - file_wrapper(&ctx, func, &method, rust_request.uri().path(), &request, &mut response, &reqheaders, rust_response.headers_mut().expect("no headers?")); + file_wrapper( + &ctx, + func, + &method, + rust_request.uri().path(), + &request, + &mut response, + &reqheaders, + rust_response.headers_mut().expect("no headers?"), + ); } } @@ -498,7 +659,7 @@ async fn process_request( // Throw away body for HEAD call let mut body = full(response.body); if method == Method::HEAD { - body = full(vec!()); + body = full(vec![]); } // Construct response based on what C++ gave us @@ -518,21 +679,47 @@ async fn process_request( ); if ctx.loglevel != rustweb::LogLevel::None { let version = format!("{:?}", version); - rustweb::log(&my_logger, rustweb::Priority::Notice, "Request", &vec!( - rustweb::KeyValue{key: "remote".to_string(), value: remote.to_string()}, - rustweb::KeyValue{key: "method".to_string(), value: method.to_string()}, - rustweb::KeyValue{key: "urlpath".to_string(), value: path.to_string()}, - rustweb::KeyValue{key: "HTTPVersion".to_string(), value: version}, - rustweb::KeyValue{key: "status".to_string(), value: response.status.to_string()}, - rustweb::KeyValue{key: "respsize".to_string(), value: len.to_string()}, - )); + rustweb::log( + &my_logger, + rustweb::Priority::Notice, + "Request", + &vec![ + rustweb::KeyValue { + key: "remote".to_string(), + value: remote.to_string(), + }, + rustweb::KeyValue { + key: "method".to_string(), + value: method.to_string(), + }, + rustweb::KeyValue { + key: "urlpath".to_string(), + value: path.to_string(), + }, + rustweb::KeyValue { + key: "HTTPVersion".to_string(), + value: version, + }, + rustweb::KeyValue { + key: "status".to_string(), + value: response.status.to_string(), + }, + rustweb::KeyValue { + key: "respsize".to_string(), + value: len.to_string(), + }, + ], + ); } Ok(rust_response) } -async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::IncomingTLS, ctx: Arc) -> MyResult<()> { - +async fn serveweb_async( + listener: TcpListener, + config: crate::web::rustweb::IncomingTLS, + ctx: Arc, +) -> MyResult<()> { if !config.certificate.is_empty() { let certs = load_certs(&config.certificate)?; let key = load_private_key(&config.key)?; @@ -553,12 +740,26 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco address = addr; let combo = rustweb::comboaddress(&address.to_string()); if !rustweb::matches(&ctx.acl, &combo) { - rustweb::log(&ctx.logger, rustweb::Priority::Debug, "No ACL match", &vec!(rustweb::KeyValue{key: "address".to_string(), value: address.to_string()})); + rustweb::log( + &ctx.logger, + rustweb::Priority::Debug, + "No ACL match", + &vec![rustweb::KeyValue { + key: "address".to_string(), + value: address.to_string(), + }], + ); continue; } } Err(err) => { - rustweb::error(&ctx.logger, rustweb::Priority::Error, &err.to_string(), "Can't get peer address", &vec!()); + rustweb::error( + &ctx.logger, + rustweb::Priority::Error, + &err.to_string(), + "Can't get peer address", + &vec![], + ); continue; // If we can't determine the peer address, don't } } @@ -568,28 +769,41 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco let tls_stream = match tls_acceptor.accept(stream).await { Ok(tls_stream) => tls_stream, Err(err) => { - rustweb::error(&ctx.logger, rustweb::Priority::Notice, &err.to_string(), "Failed to perform TLS handshake", &vec!()); + rustweb::error( + &ctx.logger, + rustweb::Priority::Notice, + &err.to_string(), + "Failed to perform TLS handshake", + &vec![], + ); continue; } }; let io = TokioIo::new(tls_stream); let my_logger = rustweb::withValue(&ctx.logger, "tls", "true"); - let fut = - http1::Builder::new().serve_connection(io, service_fn(move |req| { + let fut = http1::Builder::new().serve_connection( + io, + service_fn(move |req| { let ctx = Arc::clone(&ctx); process_request(req, ctx, address) - })); + }), + ); // Spawn a tokio task to serve the request tokio::task::spawn(async move { // Finally, we bind the incoming connection to our `process_request` service if let Err(err) = fut.await { - rustweb::error(&my_logger, rustweb::Priority::Notice, &err.to_string(), "Error serving web connection", &vec!()); + rustweb::error( + &my_logger, + rustweb::Priority::Notice, + &err.to_string(), + "Error serving web connection", + &vec![], + ); } }); } - } - else { + } else { // We start a loop to continuously accept incoming connections loop { let ctx = Arc::clone(&ctx); @@ -601,36 +815,65 @@ async fn serveweb_async(listener: TcpListener, config: crate::web::rustweb::Inco address = addr; let combo = rustweb::comboaddress(&address.to_string()); if !rustweb::matches(&ctx.acl, &combo) { - rustweb::log(&ctx.logger, rustweb::Priority::Debug, "No ACL match", &vec!(rustweb::KeyValue{key: "address".to_string(), value: address.to_string()})); + rustweb::log( + &ctx.logger, + rustweb::Priority::Debug, + "No ACL match", + &vec![rustweb::KeyValue { + key: "address".to_string(), + value: address.to_string(), + }], + ); continue; } } Err(err) => { - rustweb::error(&ctx.logger, rustweb::Priority::Error, &err.to_string(), "Can't get peer address", &vec!()); + rustweb::error( + &ctx.logger, + rustweb::Priority::Error, + &err.to_string(), + "Can't get peer address", + &vec![], + ); continue; // If we can't determine the peer address, don't } } let io = TokioIo::new(stream); let my_logger = rustweb::withValue(&ctx.logger, "tls", "false"); - let fut = - http1::Builder::new().serve_connection(io, service_fn(move |req| { + let fut = http1::Builder::new().serve_connection( + io, + service_fn(move |req| { let ctx = Arc::clone(&ctx); process_request(req, ctx, address) - })); + }), + ); // Spawn a tokio task to serve the request tokio::task::spawn(async move { // Finally, we bind the incoming connection to our `process_request` service if let Err(err) = fut.await { - rustweb::error(&my_logger, rustweb::Priority::Notice, &err.to_string(), "Error serving web connection", &vec!()); - } + rustweb::error( + &my_logger, + rustweb::Priority::Notice, + &err.to_string(), + "Error serving web connection", + &vec![], + ); + } }); } } } -pub fn serveweb(incoming: &Vec, urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr, logger: cxx::UniquePtr, loglevel: rustweb::LogLevel) -> Result<(), std::io::Error> { - +pub fn serveweb( + incoming: &Vec, + urls: &[String], + password_ch: cxx::UniquePtr, + api_ch: cxx::UniquePtr, + acl: cxx::UniquePtr, + logger: cxx::UniquePtr, + loglevel: rustweb::LogLevel, +) -> Result<(), std::io::Error> { // Context, atomically reference counted let ctx = Arc::new(Context { urls: urls.to_vec(), @@ -652,7 +895,7 @@ pub fn serveweb(incoming: &Vec, urls: &[String], pass // waits (forever) for all of them to complete by joining them all. let mut set = JoinSet::new(); for config in incoming { - rustweb::log(&ctx.logger, rustweb::Priority::Warning, "Config", &vec!()); + rustweb::log(&ctx.logger, rustweb::Priority::Warning, "Config", &vec![]); for addr_str in &config.addresses { let addr = match SocketAddr::from_str(addr_str) { Ok(val) => val, @@ -675,17 +918,35 @@ pub fn serveweb(incoming: &Vec, urls: &[String], pass if !tls.certificate.is_empty() { tls_enabled = true; } - rustweb::log(&ctx.logger, rustweb::Priority::Info, "web service listening", - &vec!(rustweb::KeyValue{key: "address".to_string(), value: addr.to_string()}, - rustweb::KeyValue{key: "tls".to_string(), value: tls_enabled.to_string()} - ) + rustweb::log( + &ctx.logger, + rustweb::Priority::Info, + "web service listening", + &vec![ + rustweb::KeyValue { + key: "address".to_string(), + value: addr.to_string(), + }, + rustweb::KeyValue { + key: "tls".to_string(), + value: tls_enabled.to_string(), + }, + ], ); set.spawn_on(serveweb_async(val, tls, ctx), runtime.handle()); } Err(err) => { let msg = format!("Unable to bind web socket: {}", err); - rustweb::error(&ctx.logger, rustweb::Priority::Error, &err.to_string(), "Unable to bind to web socket", - &vec!(rustweb::KeyValue{key: "address".to_string(), value: addr.to_string()})); + rustweb::error( + &ctx.logger, + rustweb::Priority::Error, + &err.to_string(), + "Unable to bind to web socket", + &vec![rustweb::KeyValue { + key: "address".to_string(), + value: addr.to_string(), + }], + ); return Err(std::io::Error::new(ErrorKind::Other, msg)); } } @@ -697,7 +958,13 @@ pub fn serveweb(incoming: &Vec, urls: &[String], pass runtime.block_on(async { while let Some(res) = set.join_next().await { let msg = format!("{:?}", res); - rustweb::error(&ctx.logger, rustweb::Priority::Error, &msg, "rustweb thread exited", &vec!()); + rustweb::error( + &ctx.logger, + rustweb::Priority::Error, + &msg, + "rustweb thread exited", + &vec![], + ); } }); })?; @@ -707,8 +974,12 @@ pub fn serveweb(incoming: &Vec, urls: &[String], pass // Load public certificate from file. fn load_certs(filename: &str) -> std::io::Result>> { // Open certificate file. - let certfile = std::fs::File::open(filename) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("failed to open {}: {}", filename, e)))?; + let certfile = std::fs::File::open(filename).map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("failed to open {}: {}", filename, e), + ) + })?; let mut reader = std::io::BufReader::new(certfile); // Load and return certificate. @@ -718,8 +989,12 @@ fn load_certs(filename: &str) -> std::io::Result std::io::Result> { // Open keyfile. - let keyfile = std::fs::File::open(filename) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("failed to open {}: {}", filename, e)))?; + let keyfile = std::fs::File::open(filename).map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("failed to open {}: {}", filename, e), + ) + })?; let mut reader = std::io::BufReader::new(keyfile); // Load and return a single private key. @@ -758,7 +1033,15 @@ mod rustweb { */ extern "Rust" { // The main entry point, This function will return, but will setup thread(s) to handle requests. - fn serveweb(incoming: &Vec, urls: &[String], pwch: UniquePtr, apikeych: UniquePtr, acl: UniquePtr, logger: UniquePtr, loglevel: LogLevel) -> Result<()>; + fn serveweb( + incoming: &Vec, + urls: &[String], + pwch: UniquePtr, + apikeych: UniquePtr, + acl: UniquePtr, + logger: UniquePtr, + loglevel: LogLevel, + ) -> Result<()>; } struct KeyValue { @@ -788,12 +1071,12 @@ mod rustweb { Warning = 4, Notice = 5, Info = 6, - Debug = 7 + Debug = 7, } enum LogLevel { None, Normal, - Detailed + Detailed, } /* * Functions callable from Rust @@ -807,8 +1090,14 @@ mod rustweb { fn apiServerConfig(request: &Request, response: &mut Response) -> Result<()>; fn apiServerConfigAllowFromGET(request: &Request, response: &mut Response) -> Result<()>; fn apiServerConfigAllowFromPUT(request: &Request, response: &mut Response) -> Result<()>; - fn apiServerConfigAllowNotifyFromGET(request: &Request, response: &mut Response) -> Result<()>; - fn apiServerConfigAllowNotifyFromPUT(request: &Request, response: &mut Response) -> Result<()>; + fn apiServerConfigAllowNotifyFromGET( + request: &Request, + response: &mut Response, + ) -> Result<()>; + fn apiServerConfigAllowNotifyFromPUT( + request: &Request, + response: &mut Response, + ) -> Result<()>; fn apiServerDetail(requst: &Request, response: &mut Response) -> Result<()>; fn apiServerRPZStats(request: &Request, response: &mut Response) -> Result<()>; fn apiServerSearchData(request: &Request, response: &mut Response) -> Result<()>; @@ -827,6 +1116,12 @@ mod rustweb { fn matches(nmg: &UniquePtr, address: &UniquePtr) -> bool; // match is a keyword fn withValue(logger: &UniquePtr, key: &str, val: &str) -> UniquePtr; fn log(logger: &UniquePtr, prio: Priority, msg: &str, values: &Vec); - fn error(logger: &UniquePtr, prio: Priority, err: &str, msg: &str, values: &Vec); + fn error( + logger: &UniquePtr, + prio: Priority, + err: &str, + msg: &str, + values: &Vec, + ); } } diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 9e2e1e515eb8..2cf169283f7a 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -1048,7 +1048,6 @@ static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Res } } - // Convert what we receive from Rust into C++ data, call funtions and convert results back to Rust data static void rustWrapper(const std::function& func, const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustResponse) { From 1639e31f7a1bbefb404cae34218b2fc47b97e11c Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Fri, 31 Jan 2025 16:05:37 +0100 Subject: [PATCH 24/38] Logger needs no wrapper --- pdns/recursordist/settings/cxxsupport.cc | 16 +++++++-------- pdns/recursordist/settings/rust/src/bridge.hh | 19 +++++++++--------- pdns/recursordist/settings/rust/src/web.rs | 20 +++++++++---------- pdns/recursordist/ws-recursor.cc | 2 +- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/settings/cxxsupport.cc index 6ef3378bf747..ec94e9121c8c 100644 --- a/pdns/recursordist/settings/cxxsupport.cc +++ b/pdns/recursordist/settings/cxxsupport.cc @@ -1478,7 +1478,7 @@ template template class Wrapper<::NetmaskGroup>; template class Wrapper<::ComboAddress>; -template class Wrapper>; + //template class Wrapper>; std::unique_ptr comboaddress(::rust::Str str) { @@ -1490,28 +1490,28 @@ bool matches(const std::unique_ptr& nmg, const std::unique_ptrget().match(address->get()); } -void log(const std::unique_ptr& logger, pdns::rust::web::rec::Priority log_level, ::rust::Str msg, const ::rust::Vec& values) +void log(const std::shared_ptr& logger, pdns::rust::web::rec::Priority log_level, ::rust::Str msg, const ::rust::Vec& values) { - auto log = logger->get(); + auto log = logger; for (const auto& [key, value] : values) { log = log->withValues(std::string(key), Logging::Loggable(std::string(value))); } log->info(static_cast(log_level), std::string(msg)); } -void error(const std::unique_ptr& logger, pdns::rust::web::rec::Priority log_level, ::rust::Str error, ::rust::Str msg, const ::rust::Vec& values) +void error(const std::shared_ptr& logger, pdns::rust::web::rec::Priority log_level, ::rust::Str error, ::rust::Str msg, const ::rust::Vec& values) { - auto log = logger->get(); + auto log = logger; for (const auto& [key, value] : values) { log = log->withValues(std::string(key), Logging::Loggable(std::string(value))); } log->error(static_cast(log_level), std::string(error), std::string(msg)); } -std::unique_ptr withValue(const std::unique_ptr& logger, ::rust::Str key, ::rust::Str val) +std::shared_ptr withValue(const std::shared_ptr& logger, ::rust::Str key, ::rust::Str val) { - auto ret = logger->get()->withValues(std::string(key), Logging::Loggable(std::string(val))); - return std::make_unique(ret); + auto ret = logger->withValues(std::string(key), Logging::Loggable(std::string(val))); + return ret; } } diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index b23d74872a5d..4c15177313d4 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -26,11 +26,6 @@ #include "rust/cxx.h" #include "credentials.hh" -namespace Logr -{ -class Logger; -} - namespace pdns::rust::settings::rec { uint16_t qTypeStringToCode(::rust::Str str); @@ -40,11 +35,15 @@ void setThreadName(::rust::Str str); class NetmaskGroup; union ComboAddress; +namespace Logr +{ +class Logger; +} + namespace pdns::rust::web::rec { using CredentialsHolder = ::CredentialsHolder; -// using NetmaskGroup = ::NetmaskGroup; struct KeyValue; struct Request; struct Response; @@ -71,7 +70,7 @@ private: }; using NetmaskGroup = Wrapper<::NetmaskGroup>; using ComboAddress = Wrapper<::ComboAddress>; -using Logger = Wrapper>; +using Logger = ::Logr::Logger; void apiServer(const Request& rustRequest, Response& rustResponse); void apiDiscovery(const Request& rustRequest, Response& rustResponse); @@ -96,7 +95,7 @@ void apiServerZoneDetailPUT(const Request& rustRequest, Response& rustResponse); void apiServerZoneDetailDELETE(const Request& rustRequest, Response& rustResponse); std::unique_ptr comboaddress(::rust::Str str); bool matches(const std::unique_ptr& nmg, const std::unique_ptr& address); -std::unique_ptr withValue(const std::unique_ptr& logger, ::rust::Str key, ::rust::Str val); -void log(const std::unique_ptr& logger, Priority log_level, ::rust::Str msg, const ::rust::Vec& values); -void error(const std::unique_ptr& logger, Priority log_level, ::rust::Str err, ::rust::Str msg, const ::rust::Vec& values); +std::shared_ptr withValue(const std::shared_ptr& logger, ::rust::Str key, ::rust::Str val); +void log(const std::shared_ptr& logger, Priority log_level, ::rust::Str msg, const ::rust::Vec& values); +void error(const std::shared_ptr& logger, Priority log_level, ::rust::Str err, ::rust::Str msg, const ::rust::Vec& values); } diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index 876b5a22a985..b848166c7b18 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -138,7 +138,7 @@ fn file_wrapper( } fn api_wrapper( - logger: &cxx::UniquePtr, + logger: &cxx::SharedPtr, ctx: &Context, handler: Func, request: &rustweb::Request, @@ -251,7 +251,7 @@ struct Context { password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr, - logger: cxx::UniquePtr, + logger: cxx::SharedPtr, loglevel: rustweb::LogLevel, } @@ -407,7 +407,7 @@ fn matcher( fn collect_options( path: &str, response: &mut rustweb::Response, - my_logger: &cxx::UniquePtr, + my_logger: &cxx::SharedPtr, ) { let mut methods = vec![]; for method in [Method::GET, Method::POST, Method::PUT, Method::DELETE] { @@ -504,7 +504,7 @@ fn log_request(loglevel: rustweb::LogLevel, request: &rustweb::Request, remote: fn log_response( loglevel: rustweb::LogLevel, - logger: &cxx::UniquePtr, + logger: &cxx::SharedPtr, response: &rustweb::Response, remote: SocketAddr, ) { @@ -871,7 +871,7 @@ pub fn serveweb( password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr, - logger: cxx::UniquePtr, + logger: cxx::SharedPtr, loglevel: rustweb::LogLevel, ) -> Result<(), std::io::Error> { // Context, atomically reference counted @@ -1039,7 +1039,7 @@ mod rustweb { pwch: UniquePtr, apikeych: UniquePtr, acl: UniquePtr, - logger: UniquePtr, + logger: SharedPtr, loglevel: LogLevel, ) -> Result<()>; } @@ -1054,7 +1054,7 @@ mod rustweb { uri: String, vars: Vec, parameters: Vec, - logger: &'a UniquePtr, + logger: &'a SharedPtr, } struct Response { @@ -1114,10 +1114,10 @@ mod rustweb { fn matches(self: &CredentialsHolder, str: &CxxString) -> bool; fn comboaddress(address: &str) -> UniquePtr; fn matches(nmg: &UniquePtr, address: &UniquePtr) -> bool; // match is a keyword - fn withValue(logger: &UniquePtr, key: &str, val: &str) -> UniquePtr; - fn log(logger: &UniquePtr, prio: Priority, msg: &str, values: &Vec); + fn withValue(logger: &SharedPtr, key: &str, val: &str) -> SharedPtr; + fn log(logger: &SharedPtr, prio: Priority, msg: &str, values: &Vec); fn error( - logger: &UniquePtr, + logger: &SharedPtr, prio: Priority, err: &str, msg: &str, diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 2cf169283f7a..dbd35484c56b 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -1022,7 +1022,7 @@ void serveRustWeb() acl.toMasks(::arg()["webserver-allow-from"]); auto aclPtr = std::make_unique(acl); - auto logPtr = std::make_unique(g_slog->withName("webserver")); + auto logPtr = g_slog->withName("webserver"); pdns::rust::web::rec::LogLevel loglevel = pdns::rust::web::rec::LogLevel::Normal; auto configLevel = ::arg()["webserver-loglevel"]; From ce42b61e58c3682b14dfb6c1f5a74a1b7380061e Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Fri, 31 Jan 2025 17:22:41 +0100 Subject: [PATCH 25/38] Modularize: split out misc.rs for general stuff --- pdns/recursordist/rec-main.cc | 7 - pdns/recursordist/rec-web-stubs.hh | 3 + pdns/recursordist/settings/cxxsupport.cc | 44 ++-- pdns/recursordist/settings/rust-bridge-in.rs | 9 +- .../recursordist/settings/rust-preamble-in.rs | 2 + pdns/recursordist/settings/rust/.gitignore | 1 + pdns/recursordist/settings/rust/Cargo.lock | 1 - pdns/recursordist/settings/rust/Cargo.toml | 3 +- pdns/recursordist/settings/rust/Makefile.am | 16 +- pdns/recursordist/settings/rust/build.rs | 6 +- .../recursordist/settings/rust/build_settings | 3 +- pdns/recursordist/settings/rust/src/bridge.hh | 44 ++-- pdns/recursordist/settings/rust/src/bridge.rs | 7 +- pdns/recursordist/settings/rust/src/misc.rs | 47 +++++ pdns/recursordist/settings/rust/src/web.rs | 198 ++++++++---------- pdns/recursordist/ws-recursor.cc | 11 +- pdns/recursordist/ws-recursor.hh | 12 +- pdns/webserver.hh | 2 + 18 files changed, 233 insertions(+), 183 deletions(-) create mode 100644 pdns/recursordist/settings/rust/src/misc.rs diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index b258792664d2..9e3ebb976569 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -274,8 +274,6 @@ int RecThreadInfo::runThreads(Logr::log_t log) } if (::arg().mustDo("webserver")) { - extern void serveRustWeb(); - cerr << "CALL serveRustWeb" << endl; serveRustWeb(); } @@ -358,13 +356,8 @@ int RecThreadInfo::runThreads(Logr::log_t log) info.start(currentThreadId, "web+stat", cpusMap, log); if (::arg().mustDo("webserver")) { - extern void serveRustWeb(); - cerr << "WS is CALLED " << endl; serveRustWeb(); } - else { - cerr << "WS is FALSE " << endl; - } for (auto& tInfo : RecThreadInfo::infos()) { tInfo.thread.join(); if (tInfo.exitCode != 0) { diff --git a/pdns/recursordist/rec-web-stubs.hh b/pdns/recursordist/rec-web-stubs.hh index 316d84dbabc8..60a2d3938bfc 100644 --- a/pdns/recursordist/rec-web-stubs.hh +++ b/pdns/recursordist/rec-web-stubs.hh @@ -4,6 +4,9 @@ namespace pdns::rust::web::rec #define WRAPPER(A) \ void A(const Request& /* unused */, Response& /* unused */) {} +class Request; +class Response; + WRAPPER(apiDiscovery) WRAPPER(apiDiscoveryV1) WRAPPER(apiServer) diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/settings/cxxsupport.cc index ec94e9121c8c..e41ba09db502 100644 --- a/pdns/recursordist/settings/cxxsupport.cc +++ b/pdns/recursordist/settings/cxxsupport.cc @@ -42,6 +42,7 @@ #include "iputils.hh" #include "bridge.hh" #include "settings/rust/web.rs.h" +#include "settings/rust/misc.rs.h" ::rust::Vec<::rust::String> pdns::settings::rec::getStrings(const std::string& name) { @@ -1441,24 +1442,7 @@ pdns::settings::rec::YamlSettingsStatus pdns::settings::rec::tryReadYAML(const s return yamlstatus; } -uint16_t pdns::rust::settings::rec::qTypeStringToCode(::rust::Str str) -{ - std::string tmp(str.data(), str.length()); - return QType::chartocode(tmp.c_str()); -} - -bool pdns::rust::settings::rec::isValidHostname(::rust::Str str) -{ - try { - auto name = DNSName(string(str)); - return name.isHostname(); - } - catch (...) { - return false; - } -} - -namespace pdns::rust::web::rec +namespace pdns::rust::misc { template @@ -1478,7 +1462,25 @@ template template class Wrapper<::NetmaskGroup>; template class Wrapper<::ComboAddress>; - //template class Wrapper>; + +uint16_t qTypeStringToCode(::rust::Str str) +{ + std::string tmp(str.data(), str.length()); + return QType::chartocode(tmp.c_str()); +} + +bool isValidHostname(::rust::Str str) +{ + try { + auto name = DNSName(string(str)); + return name.isHostname(); + } + catch (...) { + return false; + } +} + +void findBetterSolution(const std::unique_ptr& /* x */){}; std::unique_ptr comboaddress(::rust::Str str) { @@ -1490,7 +1492,7 @@ bool matches(const std::unique_ptr& nmg, const std::unique_ptrget().match(address->get()); } -void log(const std::shared_ptr& logger, pdns::rust::web::rec::Priority log_level, ::rust::Str msg, const ::rust::Vec& values) + void log(const std::shared_ptr& logger, pdns::rust::misc::Priority log_level, ::rust::Str msg, const ::rust::Vec& values) { auto log = logger; for (const auto& [key, value] : values) { @@ -1499,7 +1501,7 @@ void log(const std::shared_ptr& logger, pdns::rust::web::rec::Priority l log->info(static_cast(log_level), std::string(msg)); } -void error(const std::shared_ptr& logger, pdns::rust::web::rec::Priority log_level, ::rust::Str error, ::rust::Str msg, const ::rust::Vec& values) + void error(const std::shared_ptr& logger, pdns::rust::misc::Priority log_level, ::rust::Str error, ::rust::Str msg, const ::rust::Vec& values) { auto log = logger; for (const auto& [key, value] : values) { diff --git a/pdns/recursordist/settings/rust-bridge-in.rs b/pdns/recursordist/settings/rust-bridge-in.rs index 731cc15a8584..176ebad2ee39 100644 --- a/pdns/recursordist/settings/rust-bridge-in.rs +++ b/pdns/recursordist/settings/rust-bridge-in.rs @@ -310,8 +310,8 @@ pub struct IncomingTLS { certificate: String, #[serde(default, skip_serializing_if = "crate::is_default")] key: String, - #[serde(default, skip_serializing_if = "crate::is_default")] - password: String, + // #[serde(default, skip_serializing_if = "crate::is_default")] + // password: String, Not currently supported, as rusttls does not support this out of the box } #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] #[serde(deny_unknown_fields)] @@ -410,8 +410,3 @@ extern "Rust" { fn api_delete_zones(file: &str) -> Result<()>; } -unsafe extern "C++" { - include!("bridge.hh"); - fn qTypeStringToCode(name: &str) -> u16; - fn isValidHostname(name: &str) -> bool; -} diff --git a/pdns/recursordist/settings/rust-preamble-in.rs b/pdns/recursordist/settings/rust-preamble-in.rs index ee611f1533ef..d3758f6779cf 100644 --- a/pdns/recursordist/settings/rust-preamble-in.rs +++ b/pdns/recursordist/settings/rust-preamble-in.rs @@ -30,6 +30,8 @@ use helpers::*; mod bridge; use bridge::*; +mod misc; + mod web; // leaving this out causes link issues // Suppresses "Deserialize unused" warning diff --git a/pdns/recursordist/settings/rust/.gitignore b/pdns/recursordist/settings/rust/.gitignore index d31bd391e756..e9e427e1cd87 100644 --- a/pdns/recursordist/settings/rust/.gitignore +++ b/pdns/recursordist/settings/rust/.gitignore @@ -4,5 +4,6 @@ /cxx.h /lib.rs.h /web.rs.h +/misc.rs.h src/lib.rs .dir-locals.el diff --git a/pdns/recursordist/settings/rust/Cargo.lock b/pdns/recursordist/settings/rust/Cargo.lock index 4eca9bc1136e..68014e7988f1 100644 --- a/pdns/recursordist/settings/rust/Cargo.lock +++ b/pdns/recursordist/settings/rust/Cargo.lock @@ -457,7 +457,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" dependencies = [ "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", diff --git a/pdns/recursordist/settings/rust/Cargo.toml b/pdns/recursordist/settings/rust/Cargo.toml index c68288d30301..01b5e803427e 100644 --- a/pdns/recursordist/settings/rust/Cargo.toml +++ b/pdns/recursordist/settings/rust/Cargo.toml @@ -22,11 +22,12 @@ hyper-util = { version = "0.1", features = ["tokio"]} bytes = "1.8" form_urlencoded = "1.2" hyper-rustls = { version = "0.27", default-features = false } -rustls = { version = "0.23", default-features = false, features = ["ring"] } +rustls = { version = "0.23", default-features = false, features = [] } rustls-pemfile = "2.2" pki-types = { package = "rustls-pki-types", version = "1.10" } tokio-rustls = { version = "0.26", default-features = false } uuid = { version = "1.12.1", features = ["v4"] } + [build-dependencies] cxx-build = "1.0" diff --git a/pdns/recursordist/settings/rust/Makefile.am b/pdns/recursordist/settings/rust/Makefile.am index 6ade7921d08b..504518913675 100644 --- a/pdns/recursordist/settings/rust/Makefile.am +++ b/pdns/recursordist/settings/rust/Makefile.am @@ -3,20 +3,30 @@ CARGO ?= cargo all install: libsettings.a EXTRA_DIST = \ - Cargo.toml \ Cargo.lock \ + Cargo.toml \ build.rs \ src/bridge.rs \ src/helpers.rs \ + src/misc.rs \ src/web.rs # should actually end up in a target specific dir... -libsettings.a lib.rs.h web.rs.h: src/web.rs src/bridge.rs src/lib.rs src/helpers.rs Cargo.toml Cargo.lock build.rs +libsettings.a lib.rs.h web.rs.h misc.rs.h: \ + Cargo.lock \ + Cargo.toml \ + build.rs \ + src/bridge.rs \ + src/helpers.rs \ + src/lib.rs \ + src/misc.rs \ + src/web.rs SYSCONFDIR=$(sysconfdir) NODCACHEDIRNOD=$(localstatedir)/nod NODCACHEDIRUDR=$(localstatedir)/udr $(CARGO) build --release $(RUST_TARGET) --target-dir=$(builddir)/target --manifest-path ${srcdir}/Cargo.toml cp target/$(RUSTC_TARGET_ARCH)/release/libsettings.a libsettings.a cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/settings/src/lib.rs.h lib.rs.h cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/settings/src/web.rs.h web.rs.h + cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/settings/src/misc.rs.h misc.rs.h cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/rust/cxx.h cxx.h clean-local: - rm -rf libsettings.a src/lib.rs lib.rs.h web.rs.h cxx.h target + rm -rf libsettings.a src/lib.rs lib.rs.h web.rs.h cxx.h misc.rs.h target diff --git a/pdns/recursordist/settings/rust/build.rs b/pdns/recursordist/settings/rust/build.rs index cdf64ab167a5..21eb063a8d1a 100644 --- a/pdns/recursordist/settings/rust/build.rs +++ b/pdns/recursordist/settings/rust/build.rs @@ -1,9 +1,13 @@ fn main() { - let sources = vec!["src/lib.rs", "src/web.rs"]; + let sources = vec!["src/lib.rs", "src/web.rs", "src/misc.rs"]; cxx_build::bridges(sources) // .file("src/source.cc") Code callable from Rust is in ../cxxsupport.cc .flag_if_supported("-std=c++17") .flag("-Isrc") .flag("-I../../..") .compile("settings"); + + // lib.rs is genertated an take carte of by parent Makefile + println!("cargo:rerun-if-changed=src/misc.rs"); + println!("cargo:rerun-if-changed=src/web.rs"); } diff --git a/pdns/recursordist/settings/rust/build_settings b/pdns/recursordist/settings/rust/build_settings index 34c6170de0b7..073479a9da70 100755 --- a/pdns/recursordist/settings/rust/build_settings +++ b/pdns/recursordist/settings/rust/build_settings @@ -6,7 +6,6 @@ $CARGO build --release $RUST_TARGET --target-dir=$builddir/target --manifest-path $srcdir/Cargo.toml - cp -vp target/$RUSTC_TARGET_ARCH/release/libsettings.a $builddir/settings/rust/libsettings.a cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/lib.rs.h $srcdir/lib.rs.h cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/lib.rs.h $builddir/settings/rust/lib.rs.h @@ -14,3 +13,5 @@ cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/rust/cxx.h $srcdir/cxx.h cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/rust/cxx.h $builddir/settings/rust/cxx.h cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/web.rs.h $srcdir/web.rs.h cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/web.rs.h $builddir/settings/rust/web.rs.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/misc.rs.h $srcdir/misc.rs.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/misc.rs.h $builddir/settings/rust/misc.rs.h diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index 4c15177313d4..5afe3ef37ae2 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -26,13 +26,6 @@ #include "rust/cxx.h" #include "credentials.hh" -namespace pdns::rust::settings::rec -{ -uint16_t qTypeStringToCode(::rust::Str str); -bool isValidHostname(::rust::Str str); -void setThreadName(::rust::Str str); -} - class NetmaskGroup; union ComboAddress; namespace Logr @@ -40,15 +33,11 @@ namespace Logr class Logger; } - -namespace pdns::rust::web::rec -{ -using CredentialsHolder = ::CredentialsHolder; -struct KeyValue; -struct Request; -struct Response; -struct IncomingWSConfig; +namespace pdns::rust::misc { enum class Priority : uint8_t; +enum class LogLevel : uint8_t; +using Logger = ::Logr::Logger; +struct KeyValue; template class Wrapper @@ -68,9 +57,27 @@ public: private: std::unique_ptr d_ptr; }; + using NetmaskGroup = Wrapper<::NetmaskGroup>; using ComboAddress = Wrapper<::ComboAddress>; -using Logger = ::Logr::Logger; + +uint16_t qTypeStringToCode(::rust::Str str); +bool isValidHostname(::rust::Str str); +std::unique_ptr comboaddress(::rust::Str str); +bool matches(const std::unique_ptr& nmg, const std::unique_ptr& address); +std::shared_ptr withValue(const std::shared_ptr& logger, ::rust::Str key, ::rust::Str val); +void log(const std::shared_ptr& logger, Priority log_level, ::rust::Str msg, const ::rust::Vec& values); +void error(const std::shared_ptr& logger, Priority log_level, ::rust::Str err, ::rust::Str msg, const ::rust::Vec& values); +} + + +namespace pdns::rust::web::rec +{ +using CredentialsHolder = ::CredentialsHolder; +struct KeyValue; +struct Request; +struct Response; +struct IncomingWSConfig; void apiServer(const Request& rustRequest, Response& rustResponse); void apiDiscovery(const Request& rustRequest, Response& rustResponse); @@ -93,9 +100,4 @@ void apiServerSearchData(const Request& rustRequest, Response& rustResponse); void apiServerZoneDetailGET(const Request& rustRequest, Response& rustResponse); void apiServerZoneDetailPUT(const Request& rustRequest, Response& rustResponse); void apiServerZoneDetailDELETE(const Request& rustRequest, Response& rustResponse); -std::unique_ptr comboaddress(::rust::Str str); -bool matches(const std::unique_ptr& nmg, const std::unique_ptr& address); -std::shared_ptr withValue(const std::shared_ptr& logger, ::rust::Str key, ::rust::Str val); -void log(const std::shared_ptr& logger, Priority log_level, ::rust::Str msg, const ::rust::Vec& values); -void error(const std::shared_ptr& logger, Priority log_level, ::rust::Str err, ::rust::Str msg, const ::rust::Vec& values); } diff --git a/pdns/recursordist/settings/rust/src/bridge.rs b/pdns/recursordist/settings/rust/src/bridge.rs index e67931d464c6..701bdc4f9930 100644 --- a/pdns/recursordist/settings/rust/src/bridge.rs +++ b/pdns/recursordist/settings/rust/src/bridge.rs @@ -31,6 +31,7 @@ use std::sync::Mutex; use crate::helpers::OVERRIDE_TAG; use crate::recsettings::{self, *}; use crate::{Merge, ValidationError}; +use crate::misc::rustmisc; impl Default for ForwardZone { fn default() -> Self { @@ -123,9 +124,9 @@ fn is_port_number(str: &str) -> bool { pub fn validate_socket_address_or_name(field: &str, val: &String) -> Result<(), ValidationError> { let sa = validate_socket_address(field, val); - if sa.is_err() && !isValidHostname(val) { + if sa.is_err() && !rustmisc::isValidHostname(val) { let parts: Vec<&str> = val.split(':').collect(); - if parts.len() != 2 || !isValidHostname(parts[0]) || !is_port_number(parts[1]) { + if parts.len() != 2 || !rustmisc::isValidHostname(parts[0]) || !is_port_number(parts[1]) { let msg = format!( "{}: value `{}' is not an IP, IP:port, name or name:port combination", field, val @@ -137,7 +138,7 @@ pub fn validate_socket_address_or_name(field: &str, val: &String) -> Result<(), } fn validate_qtype(field: &str, val: &String) -> Result<(), ValidationError> { - let code = qTypeStringToCode(val); + let code = rustmisc::qTypeStringToCode(val); if code == 0 { let msg = format!("{}: value `{}' is not a qtype", field, val); return Err(ValidationError { msg }); diff --git a/pdns/recursordist/settings/rust/src/misc.rs b/pdns/recursordist/settings/rust/src/misc.rs new file mode 100644 index 000000000000..87118cbf9399 --- /dev/null +++ b/pdns/recursordist/settings/rust/src/misc.rs @@ -0,0 +1,47 @@ +#[cxx::bridge(namespace = "pdns::rust::misc")] +pub mod rustmisc { + + pub enum LogLevel { + None, + Normal, + Detailed, + } + enum Priority { + Absent = 0, + Alert = 1, + Critical = 2, + Error = 3, + Warning = 4, + Notice = 5, + Info = 6, + Debug = 7, + } + struct KeyValue { + key: String, + value: String, + } + + extern "C++" { + type NetmaskGroup; + type ComboAddress; + type Logger; + } + + unsafe extern "C++" { + include!("bridge.hh"); + fn qTypeStringToCode(name: &str) -> u16; + fn isValidHostname(name: &str) -> bool; + fn comboaddress(address: &str) -> UniquePtr; + fn matches(nmg: &UniquePtr, address: &UniquePtr) -> bool; // match is a keyword + fn withValue(logger: &SharedPtr, key: &str, val: &str) -> SharedPtr; + fn log(logger: &SharedPtr, prio: Priority, msg: &str, values: &Vec); + fn error( + logger: &SharedPtr, + prio: Priority, + err: &str, + msg: &str, + values: &Vec, + ); + } +} + diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index b848166c7b18..fa95835a54bb 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -28,6 +28,8 @@ use tokio::net::TcpListener; use tokio::runtime::Builder; use tokio::task::JoinSet; +use crate::misc::rustmisc; + type GenericError = Box; type MyResult = std::result::Result; type BoxBody = http_body_util::combinators::BoxBody; @@ -88,11 +90,11 @@ fn nonapi_wrapper( ) { let auth_ok = compare_authorization(ctx, reqheaders); if !auth_ok { - rustweb::log( + rustmisc::log( request.logger, rustweb::Priority::Debug, "Authentication failed", - &vec![rustweb::KeyValue { + &vec![rustmisc::KeyValue { key: "urlpath".to_string(), value: request.uri.to_owned(), }], @@ -122,11 +124,11 @@ fn file_wrapper( ) { let auth_ok = compare_authorization(ctx, reqheaders); if !auth_ok { - rustweb::log( + rustmisc::log( request.logger, rustweb::Priority::Debug, "Authentication failed", - &vec![rustweb::KeyValue { + &vec![rustmisc::KeyValue { key: "urlpath".to_string(), value: request.uri.to_owned(), }], @@ -153,11 +155,11 @@ fn api_wrapper( header::HeaderValue::from_static("*"), ); if ctx.api_ch.is_null() { - rustweb::log( + rustmisc::log( logger, rustweb::Priority::Error, "Authentication failed, API Key missing in config", - &vec![rustweb::KeyValue { + &vec![rustmisc::KeyValue { key: "urlpath".to_string(), value: request.uri.to_owned(), }], @@ -185,11 +187,11 @@ fn api_wrapper( if !auth_ok && allow_password { auth_ok = compare_authorization(ctx, reqheaders); if !auth_ok { - rustweb::log( + rustmisc::log( logger, rustweb::Priority::Debug, "Authentication failed", - &vec![rustweb::KeyValue { + &vec![rustmisc::KeyValue { key: "urlpath".to_string(), value: request.uri.to_owned(), }], @@ -199,11 +201,11 @@ fn api_wrapper( } } if !auth_ok { - rustweb::log( + rustmisc::log( logger, rustweb::Priority::Error, "Authentication failed", - &vec![rustweb::KeyValue { + &vec![rustmisc::KeyValue { key: "urlpath".to_string(), value: request.uri.to_owned(), }], @@ -250,9 +252,9 @@ struct Context { urls: Vec, password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, - acl: cxx::UniquePtr, - logger: cxx::SharedPtr, - loglevel: rustweb::LogLevel, + acl: cxx::UniquePtr, + logger: cxx::SharedPtr, + loglevel: rustmisc::LogLevel, } // Serve a file @@ -272,16 +274,16 @@ fn file( .iter() .position(|x| String::from("/") + x == uripath); if pos.is_none() { - rustweb::log( + rustmisc::log( request.logger, rustweb::Priority::Debug, "not found", &vec![ - rustweb::KeyValue { + rustmisc::KeyValue { key: "method".to_string(), value: method.to_string(), }, - rustweb::KeyValue { + rustmisc::KeyValue { key: "uripath".to_string(), value: uripath.to_string(), }, @@ -294,16 +296,16 @@ fn file( // Return 404 not found response. response.status = StatusCode::NOT_FOUND.as_u16(); response.body = NOTFOUND.to_vec(); - rustweb::log( + rustmisc::log( request.logger, rustweb::Priority::Debug, "not found case 2", &vec![ - rustweb::KeyValue { + rustmisc::KeyValue { key: "method".to_string(), value: method.to_string(), }, - rustweb::KeyValue { + rustmisc::KeyValue { key: "uripath".to_string(), value: uripath.to_string(), }, @@ -407,7 +409,7 @@ fn matcher( fn collect_options( path: &str, response: &mut rustweb::Response, - my_logger: &cxx::SharedPtr, + my_logger: &cxx::SharedPtr, ) { let mut methods = vec![]; for method in [Method::GET, Method::POST, Method::PUT, Method::DELETE] { @@ -465,17 +467,17 @@ fn collect_options( }); } -fn log_request(loglevel: rustweb::LogLevel, request: &rustweb::Request, remote: SocketAddr) { - if loglevel != rustweb::LogLevel::Detailed { +fn log_request(loglevel: rustmisc::LogLevel, request: &rustweb::Request, remote: SocketAddr) { + if loglevel != rustmisc::LogLevel::Detailed { return; } let body = std::str::from_utf8(&request.body).unwrap_or("error: body is not utf8"); let mut vec = vec![ - rustweb::KeyValue { + rustmisc::KeyValue { key: "remote".to_string(), value: remote.to_string(), }, - rustweb::KeyValue { + rustmisc::KeyValue { key: "body".to_string(), value: body.to_string(), }, @@ -490,34 +492,34 @@ fn log_request(loglevel: rustweb::LogLevel, request: &rustweb::Request, remote: let snippet = var.key.to_owned() + "=" + &var.value; str.push_str(&snippet); } - vec.push(rustweb::KeyValue { + vec.push(rustmisc::KeyValue { key: "getVars".to_string(), value: str.to_string(), }); - rustweb::log( + rustmisc::log( request.logger, - rustweb::Priority::Info, + rustmisc::Priority::Info, "Request details", &vec, ); } fn log_response( - loglevel: rustweb::LogLevel, - logger: &cxx::SharedPtr, + loglevel: rustmisc::LogLevel, + logger: &cxx::SharedPtr, response: &rustweb::Response, remote: SocketAddr, ) { - if loglevel != rustweb::LogLevel::Detailed { + if loglevel != rustmisc::LogLevel::Detailed { return; } let body = std::str::from_utf8(&response.body).unwrap_or("error: body is not utf8"); let mut vec = vec![ - rustweb::KeyValue { + rustmisc::KeyValue { key: "remote".to_string(), value: remote.to_string(), }, - rustweb::KeyValue { + rustmisc::KeyValue { key: "body".to_string(), value: body.to_string(), }, @@ -532,11 +534,11 @@ fn log_response( let snippet = var.key.to_owned() + "=" + &var.value; str.push_str(&snippet); } - vec.push(rustweb::KeyValue { + vec.push(rustmisc::KeyValue { key: "headers".to_string(), value: str.to_string(), }); - rustweb::log(logger, rustweb::Priority::Info, "Response details", &vec); + rustmisc::log(logger, rustmisc::Priority::Info, "Response details", &vec); } // Main entry point after a request arrived @@ -546,7 +548,7 @@ async fn process_request( remote: SocketAddr, ) -> MyResult> { let unique = uuid::Uuid::new_v4(); - let my_logger = rustweb::withValue(&ctx.logger, "uniqueid", &unique.to_string()); + let my_logger = rustmisc::withValue(&ctx.logger, "uniqueid", &unique.to_string()); // Convert query part of URI into vars table let mut vars: Vec = vec![]; @@ -677,34 +679,34 @@ async fn process_request( header::CONNECTION, header::HeaderValue::from_str("close").unwrap(), ); - if ctx.loglevel != rustweb::LogLevel::None { + if ctx.loglevel != rustmisc::LogLevel::None { let version = format!("{:?}", version); - rustweb::log( + rustmisc::log( &my_logger, rustweb::Priority::Notice, "Request", &vec![ - rustweb::KeyValue { + rustmisc::KeyValue { key: "remote".to_string(), value: remote.to_string(), }, - rustweb::KeyValue { + rustmisc::KeyValue { key: "method".to_string(), value: method.to_string(), }, - rustweb::KeyValue { + rustmisc::KeyValue { key: "urlpath".to_string(), value: path.to_string(), }, - rustweb::KeyValue { + rustmisc::KeyValue { key: "HTTPVersion".to_string(), value: version, }, - rustweb::KeyValue { + rustmisc::KeyValue { key: "status".to_string(), value: response.status.to_string(), }, - rustweb::KeyValue { + rustmisc::KeyValue { key: "respsize".to_string(), value: len.to_string(), }, @@ -738,13 +740,13 @@ async fn serveweb_async( match stream.peer_addr() { Ok(addr) => { address = addr; - let combo = rustweb::comboaddress(&address.to_string()); - if !rustweb::matches(&ctx.acl, &combo) { - rustweb::log( + let combo = rustmisc::comboaddress(&address.to_string()); + if !rustmisc::matches(&ctx.acl, &combo) { + rustmisc::log( &ctx.logger, rustweb::Priority::Debug, "No ACL match", - &vec![rustweb::KeyValue { + &vec![rustmisc::KeyValue { key: "address".to_string(), value: address.to_string(), }], @@ -753,7 +755,7 @@ async fn serveweb_async( } } Err(err) => { - rustweb::error( + rustmisc::error( &ctx.logger, rustweb::Priority::Error, &err.to_string(), @@ -769,7 +771,7 @@ async fn serveweb_async( let tls_stream = match tls_acceptor.accept(stream).await { Ok(tls_stream) => tls_stream, Err(err) => { - rustweb::error( + rustmisc::error( &ctx.logger, rustweb::Priority::Notice, &err.to_string(), @@ -780,7 +782,7 @@ async fn serveweb_async( } }; let io = TokioIo::new(tls_stream); - let my_logger = rustweb::withValue(&ctx.logger, "tls", "true"); + let my_logger = rustmisc::withValue(&ctx.logger, "tls", "true"); let fut = http1::Builder::new().serve_connection( io, service_fn(move |req| { @@ -793,7 +795,7 @@ async fn serveweb_async( tokio::task::spawn(async move { // Finally, we bind the incoming connection to our `process_request` service if let Err(err) = fut.await { - rustweb::error( + rustmisc::error( &my_logger, rustweb::Priority::Notice, &err.to_string(), @@ -813,13 +815,13 @@ async fn serveweb_async( match stream.peer_addr() { Ok(addr) => { address = addr; - let combo = rustweb::comboaddress(&address.to_string()); - if !rustweb::matches(&ctx.acl, &combo) { - rustweb::log( + let combo = rustmisc::comboaddress(&address.to_string()); + if !rustmisc::matches(&ctx.acl, &combo) { + rustmisc::log( &ctx.logger, rustweb::Priority::Debug, "No ACL match", - &vec![rustweb::KeyValue { + &vec![rustmisc::KeyValue { key: "address".to_string(), value: address.to_string(), }], @@ -828,9 +830,9 @@ async fn serveweb_async( } } Err(err) => { - rustweb::error( + rustmisc::error( &ctx.logger, - rustweb::Priority::Error, + rustmisc::Priority::Error, &err.to_string(), "Can't get peer address", &vec![], @@ -839,7 +841,7 @@ async fn serveweb_async( } } let io = TokioIo::new(stream); - let my_logger = rustweb::withValue(&ctx.logger, "tls", "false"); + let my_logger = rustmisc::withValue(&ctx.logger, "tls", "false"); let fut = http1::Builder::new().serve_connection( io, service_fn(move |req| { @@ -852,9 +854,9 @@ async fn serveweb_async( tokio::task::spawn(async move { // Finally, we bind the incoming connection to our `process_request` service if let Err(err) = fut.await { - rustweb::error( + rustmisc::error( &my_logger, - rustweb::Priority::Notice, + rustmisc::Priority::Notice, &err.to_string(), "Error serving web connection", &vec![], @@ -870,9 +872,9 @@ pub fn serveweb( urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, - acl: cxx::UniquePtr, - logger: cxx::SharedPtr, - loglevel: rustweb::LogLevel, + acl: cxx::UniquePtr, + logger: cxx::SharedPtr, + loglevel: rustmisc::LogLevel, ) -> Result<(), std::io::Error> { // Context, atomically reference counted let ctx = Arc::new(Context { @@ -895,7 +897,7 @@ pub fn serveweb( // waits (forever) for all of them to complete by joining them all. let mut set = JoinSet::new(); for config in incoming { - rustweb::log(&ctx.logger, rustweb::Priority::Warning, "Config", &vec![]); + rustmisc::log(&ctx.logger, rustweb::Priority::Warning, "Config", &vec![]); for addr_str in &config.addresses { let addr = match SocketAddr::from_str(addr_str) { Ok(val) => val, @@ -913,21 +915,21 @@ pub fn serveweb( let tls = crate::web::rustweb::IncomingTLS { certificate: config.tls.certificate.clone(), key: config.tls.key.clone(), - password: config.tls.password.clone(), + // password: config.tls.password.clone(), not supported (yet), ruttls does not handle it }; if !tls.certificate.is_empty() { tls_enabled = true; } - rustweb::log( + rustmisc::log( &ctx.logger, rustweb::Priority::Info, "web service listening", &vec![ - rustweb::KeyValue { + rustmisc::KeyValue { key: "address".to_string(), value: addr.to_string(), }, - rustweb::KeyValue { + rustmisc::KeyValue { key: "tls".to_string(), value: tls_enabled.to_string(), }, @@ -937,12 +939,12 @@ pub fn serveweb( } Err(err) => { let msg = format!("Unable to bind web socket: {}", err); - rustweb::error( + rustmisc::error( &ctx.logger, rustweb::Priority::Error, &err.to_string(), "Unable to bind to web socket", - &vec![rustweb::KeyValue { + &vec![rustmisc::KeyValue { key: "address".to_string(), value: addr.to_string(), }], @@ -958,9 +960,9 @@ pub fn serveweb( runtime.block_on(async { while let Some(res) = set.join_next().await { let msg = format!("{:?}", res); - rustweb::error( + rustmisc::error( &ctx.logger, - rustweb::Priority::Error, + rustmisc::Priority::Error, &msg, "rustweb thread exited", &vec![], @@ -1004,24 +1006,31 @@ fn load_private_key(filename: &str) -> std::io::Result, } - enum Priority { - Absent = 0, - Alert = 1, - Critical = 2, - Error = 3, - Warning = 4, - Notice = 5, - Info = 6, - Debug = 7, - } - enum LogLevel { - None, - Normal, - Detailed, - } /* * Functions callable from Rust */ unsafe extern "C++" { include!("bridge.hh"); + fn matches(self: &CredentialsHolder, str: &CxxString) -> bool; fn apiDiscovery(request: &Request, response: &mut Response) -> Result<()>; fn apiDiscoveryV1(request: &Request, response: &mut Response) -> Result<()>; fn apiServer(request: &Request, response: &mut Response) -> Result<()>; @@ -1110,18 +1105,5 @@ mod rustweb { fn jsonstat(request: &Request, response: &mut Response) -> Result<()>; fn prometheusMetrics(request: &Request, response: &mut Response) -> Result<()>; fn serveStuff(request: &Request, response: &mut Response) -> Result<()>; - - fn matches(self: &CredentialsHolder, str: &CxxString) -> bool; - fn comboaddress(address: &str) -> UniquePtr; - fn matches(nmg: &UniquePtr, address: &UniquePtr) -> bool; // match is a keyword - fn withValue(logger: &SharedPtr, key: &str, val: &str) -> SharedPtr; - fn log(logger: &SharedPtr, prio: Priority, msg: &str, values: &Vec); - fn error( - logger: &SharedPtr, - prio: Priority, - err: &str, - msg: &str, - values: &Vec, - ); } } diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index dbd35484c56b..b03a0c17309f 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -46,6 +46,7 @@ #include "settings/cxxsettings.hh" // IWYU pragma: keep, needed by included generated file #include "settings/rust/src/bridge.hh" #include "settings/rust/web.rs.h" +#include "settings/rust/misc.rs.h" using json11::Json; @@ -994,7 +995,7 @@ void serveRustWeb() for (const auto& address : listen.addresses) { tmp.addresses.emplace_back(address); } - tmp.tls = pdns::rust::web::rec::IncomingTLS{listen.tls.certificate, listen.tls.key, listen.tls.password}; + tmp.tls = pdns::rust::web::rec::IncomingTLS{listen.tls.certificate, listen.tls.key}; config.emplace_back(tmp); } } @@ -1020,17 +1021,17 @@ void serveRustWeb() } NetmaskGroup acl; acl.toMasks(::arg()["webserver-allow-from"]); - auto aclPtr = std::make_unique(acl); + auto aclPtr = std::make_unique(acl); auto logPtr = g_slog->withName("webserver"); - pdns::rust::web::rec::LogLevel loglevel = pdns::rust::web::rec::LogLevel::Normal; + pdns::rust::misc::LogLevel loglevel = pdns::rust::misc::LogLevel::Normal; auto configLevel = ::arg()["webserver-loglevel"]; if (configLevel == "none") { - loglevel = pdns::rust::web::rec::LogLevel::Normal; + loglevel = pdns::rust::misc::LogLevel::Normal; } else if (configLevel == "detailed") { - loglevel = pdns::rust::web::rec::LogLevel::Detailed; + loglevel = pdns::rust::misc::LogLevel::Detailed; } pdns::rust::web::rec::serveweb(config, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr), std::move(logPtr), loglevel); } diff --git a/pdns/recursordist/ws-recursor.hh b/pdns/recursordist/ws-recursor.hh index 882d29ba4fb3..4be93d07263a 100644 --- a/pdns/recursordist/ws-recursor.hh +++ b/pdns/recursordist/ws-recursor.hh @@ -20,15 +20,19 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once -#include -#include "namespaces.hh" -#include "mplexer.hh" + #include "webserver.hh" class HttpRequest; class HttpResponse; +extern void serveRustWeb(); + +#ifndef RUST_WS + +#include +#include "namespaces.hh" +#include "mplexer.hh" -#if 0 class AsyncServer : public Server { public: diff --git a/pdns/webserver.hh b/pdns/webserver.hh index e542bc54f9b1..767f986d3ec0 100644 --- a/pdns/webserver.hh +++ b/pdns/webserver.hh @@ -21,6 +21,8 @@ */ #pragma once +#include "config.h" + #ifdef RECURSOR // Network facing/routing part of webserver is implemented in rust. We stil use a few classes from // yahttp, but do not link to it. From 355990a6b9f5305fd64879811b9913585afd0bc6 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Mon, 3 Feb 2025 11:40:07 +0100 Subject: [PATCH 26/38] Reformat and add clippy comment --- pdns/recursordist/settings/cxxsupport.cc | 4 ++-- pdns/recursordist/settings/rust/src/bridge.hh | 4 ++-- pdns/recursordist/settings/rust/src/bridge.rs | 2 +- pdns/recursordist/settings/rust/src/misc.rs | 3 +-- pdns/recursordist/settings/rust/src/web.rs | 2 ++ 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/settings/cxxsupport.cc index e41ba09db502..ea27ea26e199 100644 --- a/pdns/recursordist/settings/cxxsupport.cc +++ b/pdns/recursordist/settings/cxxsupport.cc @@ -1492,7 +1492,7 @@ bool matches(const std::unique_ptr& nmg, const std::unique_ptrget().match(address->get()); } - void log(const std::shared_ptr& logger, pdns::rust::misc::Priority log_level, ::rust::Str msg, const ::rust::Vec& values) +void log(const std::shared_ptr& logger, pdns::rust::misc::Priority log_level, ::rust::Str msg, const ::rust::Vec& values) { auto log = logger; for (const auto& [key, value] : values) { @@ -1501,7 +1501,7 @@ bool matches(const std::unique_ptr& nmg, const std::unique_ptrinfo(static_cast(log_level), std::string(msg)); } - void error(const std::shared_ptr& logger, pdns::rust::misc::Priority log_level, ::rust::Str error, ::rust::Str msg, const ::rust::Vec& values) +void error(const std::shared_ptr& logger, pdns::rust::misc::Priority log_level, ::rust::Str error, ::rust::Str msg, const ::rust::Vec& values) { auto log = logger; for (const auto& [key, value] : values) { diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/settings/rust/src/bridge.hh index 5afe3ef37ae2..c716fdd90679 100644 --- a/pdns/recursordist/settings/rust/src/bridge.hh +++ b/pdns/recursordist/settings/rust/src/bridge.hh @@ -33,7 +33,8 @@ namespace Logr class Logger; } -namespace pdns::rust::misc { +namespace pdns::rust::misc +{ enum class Priority : uint8_t; enum class LogLevel : uint8_t; using Logger = ::Logr::Logger; @@ -70,7 +71,6 @@ void log(const std::shared_ptr& logger, Priority log_level, ::rust::Str void error(const std::shared_ptr& logger, Priority log_level, ::rust::Str err, ::rust::Str msg, const ::rust::Vec& values); } - namespace pdns::rust::web::rec { using CredentialsHolder = ::CredentialsHolder; diff --git a/pdns/recursordist/settings/rust/src/bridge.rs b/pdns/recursordist/settings/rust/src/bridge.rs index 701bdc4f9930..4167b9758db1 100644 --- a/pdns/recursordist/settings/rust/src/bridge.rs +++ b/pdns/recursordist/settings/rust/src/bridge.rs @@ -29,9 +29,9 @@ use std::str::FromStr; use std::sync::Mutex; use crate::helpers::OVERRIDE_TAG; +use crate::misc::rustmisc; use crate::recsettings::{self, *}; use crate::{Merge, ValidationError}; -use crate::misc::rustmisc; impl Default for ForwardZone { fn default() -> Self { diff --git a/pdns/recursordist/settings/rust/src/misc.rs b/pdns/recursordist/settings/rust/src/misc.rs index 87118cbf9399..f3999afa0abc 100644 --- a/pdns/recursordist/settings/rust/src/misc.rs +++ b/pdns/recursordist/settings/rust/src/misc.rs @@ -42,6 +42,5 @@ pub mod rustmisc { msg: &str, values: &Vec, ); - } + } } - diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/settings/rust/src/web.rs index fa95835a54bb..e9a394d5a4a3 100644 --- a/pdns/recursordist/settings/rust/src/web.rs +++ b/pdns/recursordist/settings/rust/src/web.rs @@ -1058,6 +1058,8 @@ mod rustweb { value: String, } + // Clippy does not seem to understand what cxx does and complains about needless_lifetimes + // I was unable to silence that warning struct Request<'a> { body: Vec, uri: String, From 6241458b5f0706cf22f68178aaf4c4e49c5edd20 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Mon, 3 Feb 2025 12:51:09 +0100 Subject: [PATCH 27/38] Change directory structure: rename settings subdir int rec-rust-lib, libsettings.a into librecrust.a --- pdns/recursordist/Makefile.am | 24 ++++----- pdns/recursordist/configure.ac | 4 +- pdns/recursordist/meson.build | 18 +++---- pdns/recursordist/rec-main.cc | 4 +- .../{settings => rec-rust-lib}/.gitignore | 0 .../{settings => rec-rust-lib}/Makefile.am | 0 .../{settings => rec-rust-lib}/README.md | 0 .../cxxsettings-private.hh | 0 .../{settings => rec-rust-lib}/cxxsettings.hh | 0 .../{settings => rec-rust-lib}/cxxsupport.cc | 4 +- .../docs-new-preamble-in.rst | 0 .../docs-old-preamble-in.rst | 0 .../{settings => rec-rust-lib}/generate.py | 0 .../{settings => rec-rust-lib}/meson.build | 12 ++--- .../rust-bridge-in.rs | 0 .../rust-preamble-in.rs | 0 .../rust/.gitignore | 0 .../rust/Cargo.lock | 50 +++++++++---------- .../rust/Cargo.toml | 6 +-- .../rust/Makefile.am | 16 +++--- .../{settings => rec-rust-lib}/rust/build.rs | 0 .../rec-rust-lib/rust/build_recrust | 17 +++++++ .../rust/meson.build | 14 +++--- .../rust/src/bridge.hh | 0 .../rust/src/bridge.rs | 0 .../rust/src/helpers.rs | 0 .../rust/src/misc.rs | 0 .../rust/src/web.rs | 0 .../{settings => rec-rust-lib}/table.py | 0 pdns/recursordist/rec_channel_rec.cc | 2 +- pdns/recursordist/rec_control.cc | 2 +- pdns/recursordist/reczones.cc | 2 +- .../recursordist/settings/rust/build_settings | 17 ------- pdns/recursordist/test-settings.cc | 2 +- pdns/recursordist/ws-recursor.cc | 8 +-- 35 files changed, 101 insertions(+), 101 deletions(-) rename pdns/recursordist/{settings => rec-rust-lib}/.gitignore (100%) rename pdns/recursordist/{settings => rec-rust-lib}/Makefile.am (100%) rename pdns/recursordist/{settings => rec-rust-lib}/README.md (100%) rename pdns/recursordist/{settings => rec-rust-lib}/cxxsettings-private.hh (100%) rename pdns/recursordist/{settings => rec-rust-lib}/cxxsettings.hh (100%) rename pdns/recursordist/{settings => rec-rust-lib}/cxxsupport.cc (99%) rename pdns/recursordist/{settings => rec-rust-lib}/docs-new-preamble-in.rst (100%) rename pdns/recursordist/{settings => rec-rust-lib}/docs-old-preamble-in.rst (100%) rename pdns/recursordist/{settings => rec-rust-lib}/generate.py (100%) rename pdns/recursordist/{settings => rec-rust-lib}/meson.build (71%) rename pdns/recursordist/{settings => rec-rust-lib}/rust-bridge-in.rs (100%) rename pdns/recursordist/{settings => rec-rust-lib}/rust-preamble-in.rs (100%) rename pdns/recursordist/{settings => rec-rust-lib}/rust/.gitignore (100%) rename pdns/recursordist/{settings => rec-rust-lib}/rust/Cargo.lock (99%) rename pdns/recursordist/{settings => rec-rust-lib}/rust/Cargo.toml (94%) rename pdns/recursordist/{settings => rec-rust-lib}/rust/Makefile.am (51%) rename pdns/recursordist/{settings => rec-rust-lib}/rust/build.rs (100%) create mode 100755 pdns/recursordist/rec-rust-lib/rust/build_recrust rename pdns/recursordist/{settings => rec-rust-lib}/rust/meson.build (75%) rename pdns/recursordist/{settings => rec-rust-lib}/rust/src/bridge.hh (100%) rename pdns/recursordist/{settings => rec-rust-lib}/rust/src/bridge.rs (100%) rename pdns/recursordist/{settings => rec-rust-lib}/rust/src/helpers.rs (100%) rename pdns/recursordist/{settings => rec-rust-lib}/rust/src/misc.rs (100%) rename pdns/recursordist/{settings => rec-rust-lib}/rust/src/web.rs (100%) rename pdns/recursordist/{settings => rec-rust-lib}/table.py (100%) delete mode 100755 pdns/recursordist/settings/rust/build_settings diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 488fc8e926c7..b0bd9946d8d7 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -1,16 +1,16 @@ JSON11_LIBS = $(top_builddir)/ext/json11/libjson11.la PROBDS_LIBS = $(top_builddir)/ext/probds/libprobds.la ARC4RANDOM_LIBS = $(top_builddir)/ext/arc4random/libarc4random.la -RUST_LIBS = $(top_builddir)/settings/rust/libsettings.a $(LIBDL) +RUST_LIBS = $(top_builddir)/rec-rust-lib/rust/librecrust.a $(LIBDL) AM_CPPFLAGS = $(LUA_CFLAGS) $(YAHTTP_CFLAGS) $(BOOST_CPPFLAGS) $(LIBSODIUM_CFLAGS) $(NET_SNMP_CFLAGS) $(LIBCAP_CFLAGS) $(SANITIZER_FLAGS) -O3 -Wall -pthread -DSYSCONFDIR=\"${sysconfdir}\" $(SYSTEMD_CFLAGS) AM_CPPFLAGS += \ -I$(top_srcdir)/ext/json11 \ -I$(top_srcdir)/ext/protozero/include \ - -I$(top_srcdir)/settings \ - -I$(top_builddir)/settings \ - -I$(top_srcdir)/settings/rust/src \ + -I$(top_srcdir)/rec-rust-lib \ + -I$(top_builddir)/rec-rust-lib \ + -I$(top_srcdir)/rec-rust-lib/rust/src \ $(YAHTTP_CFLAGS) \ $(LIBCRYPTO_INCLUDES) \ -DBOOST_CONTAINER_USE_STD_EXCEPTIONS @@ -53,8 +53,8 @@ htmlfiles.h: incfiles ${srcdir}/html/* ${srcdir}/html/js/* rec-metrics-gen%h rec-prometheus-gen%h rec-snmp-gen%h rec-oids-gen%h RECURSOR-MIB%txt: metrics.py metrics_table.py RECURSOR-MIB.in $(PYTHON) metrics.py -# We explicitly build settings in two steps, as settings modifies files in the settings/rust subdir -SUBDIRS=ext settings settings/rust +# We explicitly build rec-rust-lib in two steps, as it modifies files in the rec-rust-lib/rust subdir +SUBDIRS=ext rec-rust-lib rec-rust-lib/rust if LUA AM_CPPFLAGS +=$(LUA_CFLAGS) @@ -188,6 +188,7 @@ pdns_recursor_SOURCES = \ rec-main.hh rec-main.cc \ rec-protozero.cc rec-protozero.hh \ rec-responsestats.hh rec-responsestats.cc \ + rec-rust-lib/cxxsupport.cc \ rec-snmp.hh rec-snmp.cc \ rec-system-resolve.hh rec-system-resolve.cc \ rec-taskqueue.cc rec-taskqueue.hh \ @@ -211,7 +212,6 @@ pdns_recursor_SOURCES = \ rpzloader.cc rpzloader.hh \ secpoll-recursor.cc secpoll-recursor.hh \ secpoll.cc secpoll.hh \ - settings/cxxsupport.cc \ sha.hh \ sholder.hh \ shuffle.cc shuffle.hh \ @@ -242,7 +242,7 @@ pdns_recursor_SOURCES = \ zoneparser-tng.cc zoneparser-tng.hh nodist_pdns_recursor_SOURCES = \ - settings/cxxsettings-generated.cc + rec-rust-lib/cxxsettings-generated.cc if !HAVE_LUA_HPP BUILT_SOURCES += lua.hpp @@ -324,6 +324,7 @@ testrunner_SOURCES = \ rcpgenerator.cc \ rec-eventtrace.cc rec-eventtrace.hh \ rec-responsestats.hh rec-responsestats.cc \ + rec-rust-lib/cxxsupport.cc \ rec-system-resolve.hh rec-system-resolve.cc \ rec-taskqueue.cc rec-taskqueue.hh \ rec-tcounters.cc rec-tcounters.hh \ @@ -337,7 +338,6 @@ testrunner_SOURCES = \ root-dnssec.hh \ rpzloader.cc rpzloader.hh \ secpoll.cc \ - settings/cxxsupport.cc \ sholder.hh \ shuffle.cc shuffle.hh \ sillyrecords.cc \ @@ -407,7 +407,7 @@ testrunner_SOURCES = \ zoneparser-tng.cc zoneparser-tng.hh nodist_testrunner_SOURCES = \ - settings/cxxsettings-generated.cc + rec-rust-lib/cxxsettings-generated.cc testrunner_LDFLAGS = \ $(AM_LDFLAGS) \ @@ -540,11 +540,11 @@ rec_control_SOURCES = \ qtype.cc \ rcpgenerator.cc rcpgenerator.hh \ rec-lua-conf.cc rec-lua-conf.hh \ + rec-rust-lib/cxxsupport.cc \ rec-system-resolve.cc rec-system-resolve.hh \ rec-web-stubs.hh \ rec_channel.cc rec_channel.hh \ rec_control.cc \ - settings/cxxsupport.cc \ sillyrecords.cc \ sortlist.cc sortlist.hh \ svc-records.cc svc-records.hh \ @@ -552,7 +552,7 @@ rec_control_SOURCES = \ unix_utility.cc nodist_rec_control_SOURCES = \ - settings/cxxsettings-generated.cc + rec-rust-lib/cxxsettings-generated.cc dnslabeltext.cc: dnslabeltext.rl $(AM_V_GEN)$(RAGEL) $< -o dnslabeltext.cc diff --git a/pdns/recursordist/configure.ac b/pdns/recursordist/configure.ac index db122a4d95e7..84432232fde0 100644 --- a/pdns/recursordist/configure.ac +++ b/pdns/recursordist/configure.ac @@ -192,8 +192,8 @@ AC_CONFIG_FILES([Makefile ext/probds/Makefile ext/yahttp/Makefile ext/yahttp/yahttp/Makefile - settings/Makefile - settings/rust/Makefile]) + rec-rust-lib/Makefile + rec-rust-lib/rust/Makefile]) AC_OUTPUT diff --git a/pdns/recursordist/meson.build b/pdns/recursordist/meson.build index 5f276d25fc8e..1d2280c73ae4 100644 --- a/pdns/recursordist/meson.build +++ b/pdns/recursordist/meson.build @@ -74,7 +74,7 @@ subdir('meson' / 'libresolv') # res_query subdir('meson' / 'dnstap') # DNSTAP through libfstream subdir('meson' / 'libcurl') # Curl -subdir('settings') +subdir('rec-rust-lib') common_sources = [] @@ -299,7 +299,7 @@ dep_metrics = declare_dependency( deps = [ dep_pdns, dep_no_config_in_source, - dep_rust_settings, + dep_rust_recrust, dep_boost, dep_boost_context, dep_threads, @@ -397,7 +397,7 @@ librec_common = declare_dependency( config_h, dependencies: [ deps, - dep_settings_ch, + dep_recrust_ch, librec_dnslabeltext, ], ) @@ -424,8 +424,8 @@ tools = { dep_protozero, dep_yahttp_header_only, dep_json11, - dep_settings, - dep_rust_settings, + dep_recrust, + dep_rust_recrust, dep_systemd, librec_signers_openssl, librec_signers_sodium, @@ -437,8 +437,8 @@ tools = { 'manpages': ['rec_control.1'], 'deps-extra': [ dep_boost, - dep_settings, - dep_rust_settings, + dep_recrust, + dep_rust_recrust, ], }, } @@ -511,8 +511,8 @@ if get_option('unit-tests') dep_boost_test, dep_lua, dep_nod, - dep_settings, - dep_rust_settings, + dep_recrust, + dep_rust_recrust, librec_signers_openssl, librec_signers_sodium, ], diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index 9e3ebb976569..0c2de94667e0 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -39,12 +39,12 @@ #include "secpoll-recursor.hh" #include "logging.hh" #include "dnsseckeeper.hh" -#include "settings/cxxsettings.hh" +#include "rec-rust-lib/cxxsettings.hh" #include "json.hh" #include "rec-system-resolve.hh" #include "root-dnssec.hh" #include "ratelimitedlog.hh" -#include "settings/rust/web.rs.h" +#include "rec-rust-lib/rust/web.rs.h" #ifdef NOD_ENABLED #include "nod.hh" diff --git a/pdns/recursordist/settings/.gitignore b/pdns/recursordist/rec-rust-lib/.gitignore similarity index 100% rename from pdns/recursordist/settings/.gitignore rename to pdns/recursordist/rec-rust-lib/.gitignore diff --git a/pdns/recursordist/settings/Makefile.am b/pdns/recursordist/rec-rust-lib/Makefile.am similarity index 100% rename from pdns/recursordist/settings/Makefile.am rename to pdns/recursordist/rec-rust-lib/Makefile.am diff --git a/pdns/recursordist/settings/README.md b/pdns/recursordist/rec-rust-lib/README.md similarity index 100% rename from pdns/recursordist/settings/README.md rename to pdns/recursordist/rec-rust-lib/README.md diff --git a/pdns/recursordist/settings/cxxsettings-private.hh b/pdns/recursordist/rec-rust-lib/cxxsettings-private.hh similarity index 100% rename from pdns/recursordist/settings/cxxsettings-private.hh rename to pdns/recursordist/rec-rust-lib/cxxsettings-private.hh diff --git a/pdns/recursordist/settings/cxxsettings.hh b/pdns/recursordist/rec-rust-lib/cxxsettings.hh similarity index 100% rename from pdns/recursordist/settings/cxxsettings.hh rename to pdns/recursordist/rec-rust-lib/cxxsettings.hh diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/rec-rust-lib/cxxsupport.cc similarity index 99% rename from pdns/recursordist/settings/cxxsupport.cc rename to pdns/recursordist/rec-rust-lib/cxxsupport.cc index ea27ea26e199..bbc7f60dd194 100644 --- a/pdns/recursordist/settings/cxxsupport.cc +++ b/pdns/recursordist/rec-rust-lib/cxxsupport.cc @@ -41,8 +41,8 @@ #include "threadname.hh" #include "iputils.hh" #include "bridge.hh" -#include "settings/rust/web.rs.h" -#include "settings/rust/misc.rs.h" +#include "rec-rust-lib/rust/web.rs.h" +#include "rec-rust-lib/rust/misc.rs.h" ::rust::Vec<::rust::String> pdns::settings::rec::getStrings(const std::string& name) { diff --git a/pdns/recursordist/settings/docs-new-preamble-in.rst b/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst similarity index 100% rename from pdns/recursordist/settings/docs-new-preamble-in.rst rename to pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst diff --git a/pdns/recursordist/settings/docs-old-preamble-in.rst b/pdns/recursordist/rec-rust-lib/docs-old-preamble-in.rst similarity index 100% rename from pdns/recursordist/settings/docs-old-preamble-in.rst rename to pdns/recursordist/rec-rust-lib/docs-old-preamble-in.rst diff --git a/pdns/recursordist/settings/generate.py b/pdns/recursordist/rec-rust-lib/generate.py similarity index 100% rename from pdns/recursordist/settings/generate.py rename to pdns/recursordist/rec-rust-lib/generate.py diff --git a/pdns/recursordist/settings/meson.build b/pdns/recursordist/rec-rust-lib/meson.build similarity index 71% rename from pdns/recursordist/settings/meson.build rename to pdns/recursordist/rec-rust-lib/meson.build index 1ecdf79c0e7a..a38f87c5eb0e 100644 --- a/pdns/recursordist/settings/meson.build +++ b/pdns/recursordist/rec-rust-lib/meson.build @@ -13,21 +13,21 @@ generated = [ python = find_program('python3') -settings = custom_target( - command: [python, '@INPUT0@', '@SOURCE_ROOT@/settings', '@BUILD_ROOT@/settings'], +recrust = custom_target( + command: [python, '@INPUT0@', '@SOURCE_ROOT@/rec-rust-lib', '@BUILD_ROOT@/rec-rust-lib'], input: sources, output: generated, ) # librec_common depends on this, so the sources get linked -dep_settings_ch = declare_dependency( - sources: [settings, 'cxxsupport.cc'], +dep_recrust_ch = declare_dependency( + sources: [recrust, 'cxxsupport.cc'], include_directories: [include_directories('.'), ] ) # The rust parts depend on this, no sources listed, which avoid duplicates object files -# In turn deps (defined in the main meson.build file, includes dep_rust_settings) -dep_settings = declare_dependency( +# In turn deps (defined in the main meson.build file, includes dep_rust_recrust) +dep_recrust = declare_dependency( include_directories: [include_directories('.'), ] ) diff --git a/pdns/recursordist/settings/rust-bridge-in.rs b/pdns/recursordist/rec-rust-lib/rust-bridge-in.rs similarity index 100% rename from pdns/recursordist/settings/rust-bridge-in.rs rename to pdns/recursordist/rec-rust-lib/rust-bridge-in.rs diff --git a/pdns/recursordist/settings/rust-preamble-in.rs b/pdns/recursordist/rec-rust-lib/rust-preamble-in.rs similarity index 100% rename from pdns/recursordist/settings/rust-preamble-in.rs rename to pdns/recursordist/rec-rust-lib/rust-preamble-in.rs diff --git a/pdns/recursordist/settings/rust/.gitignore b/pdns/recursordist/rec-rust-lib/rust/.gitignore similarity index 100% rename from pdns/recursordist/settings/rust/.gitignore rename to pdns/recursordist/rec-rust-lib/rust/.gitignore diff --git a/pdns/recursordist/settings/rust/Cargo.lock b/pdns/recursordist/rec-rust-lib/rust/Cargo.lock similarity index 99% rename from pdns/recursordist/settings/rust/Cargo.lock rename to pdns/recursordist/rec-rust-lib/rust/Cargo.lock index 68014e7988f1..2dcdbdbe560c 100644 --- a/pdns/recursordist/settings/rust/Cargo.lock +++ b/pdns/recursordist/rec-rust-lib/rust/Cargo.lock @@ -429,6 +429,31 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "recrust" +version = "5.3.0" +dependencies = [ + "base64", + "bytes", + "cxx", + "cxx-build", + "form_urlencoded", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "once_cell", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_yml", + "tokio", + "tokio-rustls", + "uuid", +] + [[package]] name = "ring" version = "0.17.8" @@ -536,31 +561,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "settings" -version = "5.2.0" -dependencies = [ - "base64", - "bytes", - "cxx", - "cxx-build", - "form_urlencoded", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "ipnet", - "once_cell", - "rustls", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_yml", - "tokio", - "tokio-rustls", - "uuid", -] - [[package]] name = "shlex" version = "1.3.0" diff --git a/pdns/recursordist/settings/rust/Cargo.toml b/pdns/recursordist/rec-rust-lib/rust/Cargo.toml similarity index 94% rename from pdns/recursordist/settings/rust/Cargo.toml rename to pdns/recursordist/rec-rust-lib/rust/Cargo.toml index 01b5e803427e..aba4fa7f2a04 100644 --- a/pdns/recursordist/settings/rust/Cargo.toml +++ b/pdns/recursordist/rec-rust-lib/rust/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "settings" +name = "recrust" # Convention: major/minor is equal to rec's major/minor -version = "5.2.0" +version = "5.3.0" edition = "2021" [lib] -name = "settings" +name = "recrust" crate-type = ["staticlib"] [dependencies] diff --git a/pdns/recursordist/settings/rust/Makefile.am b/pdns/recursordist/rec-rust-lib/rust/Makefile.am similarity index 51% rename from pdns/recursordist/settings/rust/Makefile.am rename to pdns/recursordist/rec-rust-lib/rust/Makefile.am index 504518913675..8ff27e5ae8e5 100644 --- a/pdns/recursordist/settings/rust/Makefile.am +++ b/pdns/recursordist/rec-rust-lib/rust/Makefile.am @@ -1,6 +1,6 @@ CARGO ?= cargo -all install: libsettings.a +all install: librecrust.a EXTRA_DIST = \ Cargo.lock \ @@ -12,7 +12,7 @@ EXTRA_DIST = \ src/web.rs # should actually end up in a target specific dir... -libsettings.a lib.rs.h web.rs.h misc.rs.h: \ +librecrust.a lib.rs.h web.rs.h misc.rs.h: \ Cargo.lock \ Cargo.toml \ build.rs \ @@ -22,11 +22,11 @@ libsettings.a lib.rs.h web.rs.h misc.rs.h: \ src/misc.rs \ src/web.rs SYSCONFDIR=$(sysconfdir) NODCACHEDIRNOD=$(localstatedir)/nod NODCACHEDIRUDR=$(localstatedir)/udr $(CARGO) build --release $(RUST_TARGET) --target-dir=$(builddir)/target --manifest-path ${srcdir}/Cargo.toml - cp target/$(RUSTC_TARGET_ARCH)/release/libsettings.a libsettings.a - cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/settings/src/lib.rs.h lib.rs.h - cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/settings/src/web.rs.h web.rs.h - cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/settings/src/misc.rs.h misc.rs.h - cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/rust/cxx.h cxx.h + cp -pv target/$(RUSTC_TARGET_ARCH)/release/librecrust.a librecrust.a + cp -pv target/$(RUSTC_TARGET_ARCH)/cxxbridge/recrust/src/lib.rs.h lib.rs.h + cp -pv target/$(RUSTC_TARGET_ARCH)/cxxbridge/recrust/src/web.rs.h web.rs.h + cp -pv target/$(RUSTC_TARGET_ARCH)/cxxbridge/recrust/src/misc.rs.h misc.rs.h + cp -pv target/$(RUSTC_TARGET_ARCH)/cxxbridge/rust/cxx.h cxx.h clean-local: - rm -rf libsettings.a src/lib.rs lib.rs.h web.rs.h cxx.h misc.rs.h target + rm -rf librecrust.a src/lib.rs lib.rs.h web.rs.h cxx.h misc.rs.h target diff --git a/pdns/recursordist/settings/rust/build.rs b/pdns/recursordist/rec-rust-lib/rust/build.rs similarity index 100% rename from pdns/recursordist/settings/rust/build.rs rename to pdns/recursordist/rec-rust-lib/rust/build.rs diff --git a/pdns/recursordist/rec-rust-lib/rust/build_recrust b/pdns/recursordist/rec-rust-lib/rust/build_recrust new file mode 100755 index 000000000000..f598a38a7585 --- /dev/null +++ b/pdns/recursordist/rec-rust-lib/rust/build_recrust @@ -0,0 +1,17 @@ +#!/bin/sh -e + +#echo "PWD=$PWD" +#echo "srcdir=$srcdir" +#echo "builddir=$builddir" + +$CARGO build --release $RUST_TARGET --target-dir=$builddir/target --manifest-path $srcdir/Cargo.toml + +cp -vp target/$RUSTC_TARGET_ARCH/release/librecrust.a $builddir/rec-rust-lib/rust/librecrust.a +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/recrust/src/lib.rs.h $srcdir/lib.rs.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/recrust/src/lib.rs.h $builddir/rec-rust-lib/rust/lib.rs.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/rust/cxx.h $srcdir/cxx.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/rust/cxx.h $builddir/rec-rust-lib/rust/cxx.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/recrust/src/web.rs.h $srcdir/web.rs.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/recrust/src/web.rs.h $builddir/rec-rust-lib/rust/web.rs.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/recrust/src/misc.rs.h $srcdir/misc.rs.h +cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/recrust/src/misc.rs.h $builddir/rec-rust-lib/rust/misc.rs.h diff --git a/pdns/recursordist/settings/rust/meson.build b/pdns/recursordist/rec-rust-lib/rust/meson.build similarity index 75% rename from pdns/recursordist/settings/rust/meson.build rename to pdns/recursordist/rec-rust-lib/rust/meson.build index 014ca2927323..579514f1d3e0 100644 --- a/pdns/recursordist/settings/rust/meson.build +++ b/pdns/recursordist/rec-rust-lib/rust/meson.build @@ -1,8 +1,8 @@ -build = find_program('build_settings') +build = find_program('build_recrust') cargo = find_program('cargo') infile = 'Cargo.toml' -outfile = 'libsettings.a' +outfile = 'librecrust.a' env = environment() @@ -15,7 +15,7 @@ env.append('srcdir', meson.current_source_dir()) env.append('RUST_TARGET', '') env.append('RUSTC_TARGET_ARCH', '') -lib_settings = custom_target('libsettings.a', +lib_recrust = custom_target('librecrust.a', output: [outfile, 'cxx.h'], input: infile, command: [build, @@ -25,13 +25,13 @@ lib_settings = custom_target('libsettings.a', 'src/bridge.rs', 'src/helpers.rs', ], - depends: settings, + depends: recrust, env: env, console: true, ) -dep_rust_settings = declare_dependency( - link_with: lib_settings[0], - sources: lib_settings[1], +dep_rust_recrust = declare_dependency( + link_with: lib_recrust[0], + sources: lib_recrust[1], include_directories: [include_directories('.'), include_directories('src')], ) diff --git a/pdns/recursordist/settings/rust/src/bridge.hh b/pdns/recursordist/rec-rust-lib/rust/src/bridge.hh similarity index 100% rename from pdns/recursordist/settings/rust/src/bridge.hh rename to pdns/recursordist/rec-rust-lib/rust/src/bridge.hh diff --git a/pdns/recursordist/settings/rust/src/bridge.rs b/pdns/recursordist/rec-rust-lib/rust/src/bridge.rs similarity index 100% rename from pdns/recursordist/settings/rust/src/bridge.rs rename to pdns/recursordist/rec-rust-lib/rust/src/bridge.rs diff --git a/pdns/recursordist/settings/rust/src/helpers.rs b/pdns/recursordist/rec-rust-lib/rust/src/helpers.rs similarity index 100% rename from pdns/recursordist/settings/rust/src/helpers.rs rename to pdns/recursordist/rec-rust-lib/rust/src/helpers.rs diff --git a/pdns/recursordist/settings/rust/src/misc.rs b/pdns/recursordist/rec-rust-lib/rust/src/misc.rs similarity index 100% rename from pdns/recursordist/settings/rust/src/misc.rs rename to pdns/recursordist/rec-rust-lib/rust/src/misc.rs diff --git a/pdns/recursordist/settings/rust/src/web.rs b/pdns/recursordist/rec-rust-lib/rust/src/web.rs similarity index 100% rename from pdns/recursordist/settings/rust/src/web.rs rename to pdns/recursordist/rec-rust-lib/rust/src/web.rs diff --git a/pdns/recursordist/settings/table.py b/pdns/recursordist/rec-rust-lib/table.py similarity index 100% rename from pdns/recursordist/settings/table.py rename to pdns/recursordist/rec-rust-lib/table.py diff --git a/pdns/recursordist/rec_channel_rec.cc b/pdns/recursordist/rec_channel_rec.cc index 76fa3a6015e9..ad30bca39345 100644 --- a/pdns/recursordist/rec_channel_rec.cc +++ b/pdns/recursordist/rec_channel_rec.cc @@ -40,7 +40,7 @@ #include "rec-main.hh" #include "rec-system-resolve.hh" -#include "settings/cxxsettings.hh" +#include "rec-rust-lib/cxxsettings.hh" /* g++ defines __SANITIZE_THREAD__ clang++ supports the nice __has_feature(thread_sanitizer), diff --git a/pdns/recursordist/rec_control.cc b/pdns/recursordist/rec_control.cc index cda2f939fa33..9b65630cf894 100644 --- a/pdns/recursordist/rec_control.cc +++ b/pdns/recursordist/rec_control.cc @@ -32,7 +32,7 @@ #include "credentials.hh" #include "namespaces.hh" #include "rec_channel.hh" -#include "settings/cxxsettings.hh" +#include "rec-rust-lib/cxxsettings.hh" #include "logger.hh" #include "logging.hh" diff --git a/pdns/recursordist/reczones.cc b/pdns/recursordist/reczones.cc index 859894d70329..b92d6daed690 100644 --- a/pdns/recursordist/reczones.cc +++ b/pdns/recursordist/reczones.cc @@ -32,7 +32,7 @@ #include "logger.hh" #include "syncres.hh" #include "zoneparser-tng.hh" -#include "settings/cxxsettings.hh" +#include "rec-rust-lib/cxxsettings.hh" #include "rec-system-resolve.hh" // XXX consider including rec-main.hh? diff --git a/pdns/recursordist/settings/rust/build_settings b/pdns/recursordist/settings/rust/build_settings deleted file mode 100755 index 073479a9da70..000000000000 --- a/pdns/recursordist/settings/rust/build_settings +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -e - -#echo "PWD=$PWD" -#echo "srcdir=$srcdir" -#echo "builddir=$builddir" - -$CARGO build --release $RUST_TARGET --target-dir=$builddir/target --manifest-path $srcdir/Cargo.toml - -cp -vp target/$RUSTC_TARGET_ARCH/release/libsettings.a $builddir/settings/rust/libsettings.a -cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/lib.rs.h $srcdir/lib.rs.h -cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/lib.rs.h $builddir/settings/rust/lib.rs.h -cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/rust/cxx.h $srcdir/cxx.h -cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/rust/cxx.h $builddir/settings/rust/cxx.h -cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/web.rs.h $srcdir/web.rs.h -cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/web.rs.h $builddir/settings/rust/web.rs.h -cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/misc.rs.h $srcdir/misc.rs.h -cp -vp target/$RUSTC_TARGET_ARCH/cxxbridge/settings/src/misc.rs.h $builddir/settings/rust/misc.rs.h diff --git a/pdns/recursordist/test-settings.cc b/pdns/recursordist/test-settings.cc index 25b649df4f77..517c9edf34ec 100644 --- a/pdns/recursordist/test-settings.cc +++ b/pdns/recursordist/test-settings.cc @@ -9,7 +9,7 @@ #include #include -#include "settings/cxxsettings.hh" +#include "rec-rust-lib/cxxsettings.hh" BOOST_AUTO_TEST_SUITE(test_settings) diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index b03a0c17309f..69969d50ecb6 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -43,10 +43,10 @@ #include "rec-lua-conf.hh" #include "rpzloader.hh" #include "rec-main.hh" -#include "settings/cxxsettings.hh" // IWYU pragma: keep, needed by included generated file -#include "settings/rust/src/bridge.hh" -#include "settings/rust/web.rs.h" -#include "settings/rust/misc.rs.h" +#include "rec-rust-lib/cxxsettings.hh" // IWYU pragma: keep, needed by included generated file +#include "rec-rust-lib/rust/src/bridge.hh" +#include "rec-rust-lib/rust/web.rs.h" +#include "rec-rust-lib/rust/misc.rs.h" using json11::Json; From f202ae789a2d9fa15176483a319a9c59da83aa00 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Mon, 3 Feb 2025 13:30:54 +0100 Subject: [PATCH 28/38] Basic https test --- .../recursordist/rec-rust-lib/rust/Cargo.lock | 283 ++++++++++++++++++ .../recursordist/rec-rust-lib/rust/Cargo.toml | 2 +- regression-tests.recursor-dnssec/.gitignore | 9 + regression-tests.recursor-dnssec/Makefile | 15 + .../configCA.conf | 19 ++ .../configServer.conf | 21 ++ regression-tests.recursor-dnssec/runtests | 3 + .../test_Prometheus.py | 33 ++ 8 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 regression-tests.recursor-dnssec/Makefile create mode 100644 regression-tests.recursor-dnssec/configCA.conf create mode 100644 regression-tests.recursor-dnssec/configServer.conf diff --git a/pdns/recursordist/rec-rust-lib/rust/Cargo.lock b/pdns/recursordist/rec-rust-lib/rust/Cargo.lock index 2dcdbdbe560c..311a4e801975 100644 --- a/pdns/recursordist/rec-rust-lib/rust/Cargo.lock +++ b/pdns/recursordist/rec-rust-lib/rust/Cargo.lock @@ -17,12 +17,46 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +[[package]] +name = "aws-lc-rs" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b7ddaa2c56a367ad27a094ad8ef4faacf8a617c2575acb2ba88949df999ca" +dependencies = [ + "aws-lc-sys", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71b2ddd3ada61a305e1d8bb6c005d1eaa7d14d903681edfc400406d523a9b491" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "paste", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -44,6 +78,35 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "bytes" version = "1.8.0" @@ -56,15 +119,46 @@ version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" dependencies = [ + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24a03c8b52922d68a1589ad61032f2c1aa5a8158d2aa0d93c6e9534944bbad6" +dependencies = [ + "cc", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -119,12 +213,34 @@ dependencies = [ "syn", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "fnv" version = "1.0.7" @@ -140,6 +256,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-channel" version = "0.3.31" @@ -190,6 +312,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + [[package]] name = "hashbrown" version = "0.14.5" @@ -202,6 +330,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "http" version = "1.1.0" @@ -320,18 +457,58 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] + [[package]] name = "libyml" version = "0.0.5" @@ -351,12 +528,30 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -378,6 +573,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "object" version = "0.36.5" @@ -393,6 +598,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -411,6 +622,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -454,6 +675,35 @@ dependencies = [ "uuid", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "ring" version = "0.17.8" @@ -475,12 +725,32 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" version = "0.23.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" dependencies = [ + "aws-lc-rs", "once_cell", "rustls-pki-types", "rustls-webpki", @@ -509,6 +779,7 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -718,6 +989,18 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi-util" version = "0.1.9" diff --git a/pdns/recursordist/rec-rust-lib/rust/Cargo.toml b/pdns/recursordist/rec-rust-lib/rust/Cargo.toml index aba4fa7f2a04..8de626588986 100644 --- a/pdns/recursordist/rec-rust-lib/rust/Cargo.toml +++ b/pdns/recursordist/rec-rust-lib/rust/Cargo.toml @@ -22,7 +22,7 @@ hyper-util = { version = "0.1", features = ["tokio"]} bytes = "1.8" form_urlencoded = "1.2" hyper-rustls = { version = "0.27", default-features = false } -rustls = { version = "0.23", default-features = false, features = [] } +rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] } rustls-pemfile = "2.2" pki-types = { package = "rustls-pki-types", version = "1.10" } tokio-rustls = { version = "0.26", default-features = false } diff --git a/regression-tests.recursor-dnssec/.gitignore b/regression-tests.recursor-dnssec/.gitignore index 4b621b2f5e0b..118c63c0c448 100644 --- a/regression-tests.recursor-dnssec/.gitignore +++ b/regression-tests.recursor-dnssec/.gitignore @@ -4,3 +4,12 @@ /configs /vars /*_pb2.py +/ca.key +/ca.pem +/ca.srl +/server.chain +/server.csr +/server.key +/server.pem +/server.p12 + diff --git a/regression-tests.recursor-dnssec/Makefile b/regression-tests.recursor-dnssec/Makefile new file mode 100644 index 000000000000..84286d7a4a95 --- /dev/null +++ b/regression-tests.recursor-dnssec/Makefile @@ -0,0 +1,15 @@ +clean-certs: + rm -f ca.key ca.pem ca.srl server.csr server.key server.pem server.chain server.ocsp +clean-configs: + rm -rf configs/* +certs: + # Generate a new CA + openssl req -new -x509 -days 1 -extensions v3_ca -keyout ca.key -out ca.pem -nodes -config configCA.conf + # Generate a new server certificate request + openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr -config configServer.conf + # Sign the server cert + openssl x509 -req -days 1 -CA ca.pem -CAkey ca.key -CAcreateserial -in server.csr -out server.pem -extfile configServer.conf -extensions v3_req + # Generate a chain + cat server.pem ca.pem > server.chain + # Generate a password-protected PKCS12 file + openssl pkcs12 -export -passout pass:passw0rd -clcerts -in server.pem -CAfile ca.pem -inkey server.key -out server.p12 diff --git a/regression-tests.recursor-dnssec/configCA.conf b/regression-tests.recursor-dnssec/configCA.conf new file mode 100644 index 000000000000..353616e9101b --- /dev/null +++ b/regression-tests.recursor-dnssec/configCA.conf @@ -0,0 +1,19 @@ +[req] +default_bits = 2048 +encrypt_key = no +prompt = no +distinguished_name = distinguished_name + +[v3_ca] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +basicConstraints = critical, CA:true +keyUsage = critical, cRLSign, keyCertSign + +[distinguished_name] +CN = PowerDNS Recursor TLS regression tests CA +OU = PowerDNS.com BV +countryName = NL + +[CA_default] +copy_extensions = copy diff --git a/regression-tests.recursor-dnssec/configServer.conf b/regression-tests.recursor-dnssec/configServer.conf new file mode 100644 index 000000000000..587caf621fba --- /dev/null +++ b/regression-tests.recursor-dnssec/configServer.conf @@ -0,0 +1,21 @@ +[req] +default_bits = 2048 +encrypt_key = no +prompt = no +distinguished_name = server_distinguished_name +req_extensions = v3_req + +[server_distinguished_name] +CN = tls.tests.powerdns.com +OU = PowerDNS.com BV +countryName = NL + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = tls.tests.powerdns.com +DNS.2 = powerdns.com +IP.3 = 127.0.0.1 diff --git a/regression-tests.recursor-dnssec/runtests b/regression-tests.recursor-dnssec/runtests index 3543dd2b79c5..41baa9875c0d 100755 --- a/regression-tests.recursor-dnssec/runtests +++ b/regression-tests.recursor-dnssec/runtests @@ -18,6 +18,9 @@ protoc -I=../pdns/ --python_out=. ../pdns/dnsmessage.proto protoc -I=../pdns/ --python_out=. ../pdns/dnstap.proto +make clean-certs +make certs + mkdir -p configs [ -f ./vars ] && . ./vars diff --git a/regression-tests.recursor-dnssec/test_Prometheus.py b/regression-tests.recursor-dnssec/test_Prometheus.py index c734b27f63f0..065c803e6d2e 100644 --- a/regression-tests.recursor-dnssec/test_Prometheus.py +++ b/regression-tests.recursor-dnssec/test_Prometheus.py @@ -81,3 +81,36 @@ def testPrometheus(self): self.assertEqual(r.status_code, 200) self.checkPrometheusContentBasic(r.text) self.checkPrometheusContentPromtool(r.content) + +class HttpsPrometheusTest(RecPrometheusTest): + _confdir = 'HttpsPrometheus' + _wsPort = 8042 + _wsTimeout = 2 + _wsPassword = 'secretpassword' + _apiKey = 'secretapikey' + + _config_template = """ +webservice: + webserver: true + listen: + - addresses: [127.0.0.1:%s] + tls: + certificate: server.chain + key: server.key + password: %s + allow_from: [127.0.0.1] + api_key: %s +""" % (_wsPort, _wsPassword, _apiKey) + + @classmethod + def generateRecursorConfig(cls, confdir): + super(HttpsPrometheusTest, cls).generateRecursorYamlConfig(confdir) + + def testPrometheus(self): + self.waitForTCPSocket("127.0.0.1", self._wsPort) + url = 'https://user:' + self._wsPassword + '@127.0.0.1:' + str(self._wsPort) + '/metrics' + r = requests.get(url, timeout=self._wsTimeout, verify=False) + self.assertTrue(r) + self.assertEqual(r.status_code, 200) + self.checkPrometheusContentBasic(r.text) + self.checkPrometheusContentPromtool(r.content) From cd979ea2a15ec4ddabb0cb18125e20d4f913b0c0 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Mon, 3 Feb 2025 14:26:56 +0100 Subject: [PATCH 29/38] Run API tests using https --- regression-tests.api/.gitignore | 9 +++++++++ regression-tests.api/Makefile | 15 +++++++++++++++ regression-tests.api/configCA.conf | 19 +++++++++++++++++++ regression-tests.api/configServer.conf | 21 +++++++++++++++++++++ regression-tests.api/runtests | 3 +++ regression-tests.api/runtests.py | 19 +++++++++++++++++-- regression-tests.api/test_Basics.py | 4 ++-- regression-tests.api/test_Servers.py | 2 +- regression-tests.api/test_helper.py | 3 +++ regression-tests.recursor-dnssec/.gitignore | 1 - 10 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 regression-tests.api/Makefile create mode 100644 regression-tests.api/configCA.conf create mode 100644 regression-tests.api/configServer.conf diff --git a/regression-tests.api/.gitignore b/regression-tests.api/.gitignore index fcd61ea7b4ed..e80d95307ffe 100644 --- a/regression-tests.api/.gitignore +++ b/regression-tests.api/.gitignore @@ -16,3 +16,12 @@ /acl-notify.list.yml /acl.list.yml /recursor.yml +/rec-api.d +/ca.key +/ca.pem +/ca.srl +/server.chain +/server.csr +/server.key +/server.pem +/server.p12 diff --git a/regression-tests.api/Makefile b/regression-tests.api/Makefile new file mode 100644 index 000000000000..84286d7a4a95 --- /dev/null +++ b/regression-tests.api/Makefile @@ -0,0 +1,15 @@ +clean-certs: + rm -f ca.key ca.pem ca.srl server.csr server.key server.pem server.chain server.ocsp +clean-configs: + rm -rf configs/* +certs: + # Generate a new CA + openssl req -new -x509 -days 1 -extensions v3_ca -keyout ca.key -out ca.pem -nodes -config configCA.conf + # Generate a new server certificate request + openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr -config configServer.conf + # Sign the server cert + openssl x509 -req -days 1 -CA ca.pem -CAkey ca.key -CAcreateserial -in server.csr -out server.pem -extfile configServer.conf -extensions v3_req + # Generate a chain + cat server.pem ca.pem > server.chain + # Generate a password-protected PKCS12 file + openssl pkcs12 -export -passout pass:passw0rd -clcerts -in server.pem -CAfile ca.pem -inkey server.key -out server.p12 diff --git a/regression-tests.api/configCA.conf b/regression-tests.api/configCA.conf new file mode 100644 index 000000000000..353616e9101b --- /dev/null +++ b/regression-tests.api/configCA.conf @@ -0,0 +1,19 @@ +[req] +default_bits = 2048 +encrypt_key = no +prompt = no +distinguished_name = distinguished_name + +[v3_ca] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +basicConstraints = critical, CA:true +keyUsage = critical, cRLSign, keyCertSign + +[distinguished_name] +CN = PowerDNS Recursor TLS regression tests CA +OU = PowerDNS.com BV +countryName = NL + +[CA_default] +copy_extensions = copy diff --git a/regression-tests.api/configServer.conf b/regression-tests.api/configServer.conf new file mode 100644 index 000000000000..587caf621fba --- /dev/null +++ b/regression-tests.api/configServer.conf @@ -0,0 +1,21 @@ +[req] +default_bits = 2048 +encrypt_key = no +prompt = no +distinguished_name = server_distinguished_name +req_extensions = v3_req + +[server_distinguished_name] +CN = tls.tests.powerdns.com +OU = PowerDNS.com BV +countryName = NL + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = tls.tests.powerdns.com +DNS.2 = powerdns.com +IP.3 = 127.0.0.1 diff --git a/regression-tests.api/runtests b/regression-tests.api/runtests index 46bddcbbf20d..6e7838a1c3dd 100755 --- a/regression-tests.api/runtests +++ b/regression-tests.api/runtests @@ -8,6 +8,9 @@ python -V pip install -U pip wheel | cat pip install -r requirements.txt | cat +make clean-certs +make certs + if [ -z "${SDIG}" ]; then export SDIG=$(type -P sdig) fi diff --git a/regression-tests.api/runtests.py b/regression-tests.api/runtests.py index 0a3e8f1d578e..99932570d3eb 100755 --- a/regression-tests.api/runtests.py +++ b/regression-tests.api/runtests.py @@ -110,7 +110,15 @@ allow_from_file: acl.list.yml allow_notify_from_file: acl-notify.list.yml webservice: + webserver: true api_dir: %(api_dir)s + listen: + - addresses: [ 127.0.0.1:"""+str(WEBPORT)+""" ] + tls: + certificate: server.chain + key: server.key + api_key: """+APIKEY+""" + password: """+WEBPASSWORD+""" recursor: include_dir: %(conf_dir)s devonly_regression_test_mode: true @@ -160,6 +168,10 @@ def run_check_call(cmd, *args, **kwargs): "--webserver-password="+WEBPASSWORD, "--api-key="+APIKEY ] +rec_args = [ + "--daemon=no", "--socket-dir=.", "--config-dir=.", + "--local-address=127.0.0.1", "--local-port="+str(DNSPORT), +] # Take sdig if it exists (recursor in travis), otherwise build it from Authoritative source. sdig = os.environ.get("SDIG", "") @@ -237,7 +249,7 @@ def run_check_call(cmd, *args, **kwargs): with open(conf_dir+'/example.com.yml', 'w') as conf_file: conf_file.write(REC_EXAMPLE_COM_CONF_TPL) - servercmd = [pdns_recursor] + common_args + servercmd = [pdns_recursor] + rec_args # Now run pdns and the tests. @@ -269,7 +281,10 @@ def finalize_server(): time.sleep(1) for try_number in range(0, 10): try: - res = requests.get('http://127.0.0.1:%s/' % WEBPORT) + if daemon == 'authoritative': + res = requests.get('http://127.0.0.1:%s/' % WEBPORT) + else: + res = requests.get('https://127.0.0.1:%s/' % WEBPORT, verify=False) available = True break except HTTPError as http_err: diff --git a/regression-tests.api/test_Basics.py b/regression-tests.api/test_Basics.py index 46b32a641b46..6acf9a803283 100644 --- a/regression-tests.api/test_Basics.py +++ b/regression-tests.api/test_Basics.py @@ -7,11 +7,11 @@ class TestBasics(ApiTestCase): def test_unauth(self): - r = requests.get(self.url("/api/v1/servers/localhost")) + r = requests.get(self.url("/api/v1/servers/localhost"), verify=False) self.assertEqual(r.status_code, requests.codes.unauthorized) def test_index_html(self): - r = requests.get(self.url("/"), auth=('admin', self.server_web_password)) + r = requests.get(self.url("/"), auth=('admin', self.server_web_password), verify=False) self.assertEqual(r.status_code, requests.codes.ok) def test_split_request(self): diff --git a/regression-tests.api/test_Servers.py b/regression-tests.api/test_Servers.py index 47122ebb1593..c0e120651474 100644 --- a/regression-tests.api/test_Servers.py +++ b/regression-tests.api/test_Servers.py @@ -101,7 +101,7 @@ def test_read_metrics(self): @unittest.skipIf(is_auth(), "Not applicable") def test_read_statistics_using_password(self): - r = requests.get(self.url("/api/v1/servers/localhost/statistics"), auth=('admin', self.server_web_password)) + r = requests.get(self.url("/api/v1/servers/localhost/statistics"), auth=('admin', self.server_web_password), verify=False) self.assertEqual(r.status_code, requests.codes.ok) self.assert_success_json(r) diff --git a/regression-tests.api/test_helper.py b/regression-tests.api/test_helper.py index 54d70126287e..fe8272560fcb 100644 --- a/regression-tests.api/test_helper.py +++ b/regression-tests.api/test_helper.py @@ -38,6 +38,9 @@ def setUp(self): self.server_web_password = os.environ.get('WEBPASSWORD', 'MISSING') self.session = requests.Session() self.session.headers = {'X-API-Key': os.environ.get('APIKEY', 'changeme-key'), 'Origin': 'http://%s:%s' % (self.server_address, self.server_port)} + if is_recursor(): + self.server_url = 'https://%s:%s/' % (self.server_address, self.server_port) + self.session.verify = False def url(self, relative_url): return urljoin(self.server_url, relative_url) diff --git a/regression-tests.recursor-dnssec/.gitignore b/regression-tests.recursor-dnssec/.gitignore index 118c63c0c448..1bc206a05c3f 100644 --- a/regression-tests.recursor-dnssec/.gitignore +++ b/regression-tests.recursor-dnssec/.gitignore @@ -12,4 +12,3 @@ /server.key /server.pem /server.p12 - From fdb513a711e4fc57742e2615e02be702a11bc7d2 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Mon, 3 Feb 2025 14:57:34 +0100 Subject: [PATCH 30/38] Docs --- .github/actions/spell-check/expect.txt | 4 +- DEVELOPMENT.md | 2 +- .../rec-rust-lib/docs-new-preamble-in.rst | 29 ++++++++++++++ pdns/recursordist/rec-rust-lib/generate.py | 2 + pdns/recursordist/rec-rust-lib/table.py | 39 ++++--------------- 5 files changed, 43 insertions(+), 33 deletions(-) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 851f849ecb97..ec17629a36f6 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -917,6 +917,7 @@ Novell nproxy NPTL NSes +NSID nsid nsis nsrecord @@ -1016,9 +1017,9 @@ pickchashed pickclosest pickhashed picknamehashed -pickselfweighted pickrandom pickrandomsample +pickselfweighted pickwhashed pickwrandom piddir @@ -1498,6 +1499,7 @@ versionmodified Viala viewcode visitedlinkcolor +Vixie vixie vla Voegeli diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index ce518842409b..8b5c147ee7a4 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -46,7 +46,7 @@ make -nwk | /path/to/compiledb -o- > compile_commands.json to generate the compilation database. For the authoritative server, the configure command is run in the top level directory, while the compiledb command should be run in the `pdns` subdirectory. -# Seting up the LSP client +# Setting up the LSP client Once the compilation database is generated, you can now move onto setting up an LSP client in your editor or IDE. diff --git a/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst b/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst index 0220b5de17dc..7723373f06ea 100644 --- a/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst +++ b/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst @@ -536,6 +536,35 @@ For catalog zone members in a group, the forwarding parameters will be taken fro The forwarding definitions will be written into a file ``$api_dir/catzone.$zonename``. :ref:`setting-yaml-webservice.api_dir` must be defined, the directory must exist and be writable by the :program:`Recursor` process. +IncomingWSConfig +^^^^^^^^^^^^^^^^^^^^^ +As of version 5.3.0, an incoming web server configuration is defined as + +.. code-block:: yaml + + addresses: [] Sequence of SocketAddress + tls: + certificates: file containing full certificate chain in PEM format + key: file contaiing private key in PEM format + + +A :ref:`setting-yaml-webservice.listen` section contains a sequence of `IncomingWSConfig`_, for example: + +.. code-block:: yaml + + webservice: + listen: + - addresses: [127.0.0.1:8083, '[::]:8083'] + tls: + certificate: fullchain.pem + key: keyfile.key + - addresses: [127.0.0.1:8084, '[::]:8084'] + +If no ``tls`` section is present, plaintext ``http`` connections are accepted on the listed addresses. + +If a ``tls`` section is present, clienst are required to use ``https`` to contact any of the address-port combinations listen in addresses. At the moment it is not possible to list additional properties of the TLS listener and encrypted key files cannot be used. + + The YAML settings ----------------- diff --git a/pdns/recursordist/rec-rust-lib/generate.py b/pdns/recursordist/rec-rust-lib/generate.py index cf9dfd1ea8d7..b9ec2a71fc4d 100644 --- a/pdns/recursordist/rec-rust-lib/generate.py +++ b/pdns/recursordist/rec-rust-lib/generate.py @@ -185,6 +185,8 @@ def get_newdoc_typename(typ): return 'Sequence of `ProxyMapping`_' if typ == LType.ListForwardingCatalogZones: return 'Sequence of `ForwardingCatalogZone`_' + if typ == LType.ListIncomingWSConfigs: + return 'Sequence of `IncomingWSConfig`_' return 'Unknown2' + str(typ) def get_default_olddoc_value(typ, val): diff --git a/pdns/recursordist/rec-rust-lib/table.py b/pdns/recursordist/rec-rust-lib/table.py index dbdcd4867a8b..93600481b8eb 100644 --- a/pdns/recursordist/rec-rust-lib/table.py +++ b/pdns/recursordist/rec-rust-lib/table.py @@ -3210,8 +3210,8 @@ 'default' : '', 'help' : 'XXXX', 'doc' : ''' -XXXXX IP addresses for the webserver to listen on. -If this setting has a non-default value, :ref:`setting-yaml-webservice.address` :ref:`setting-yaml-webservice.port` and will be ignored. +IP addresses and associated attributes for the webserver to listen on. +If this setting has a non-default value, :ref:`setting-yaml-webservice.address` and :ref:`setting-yaml-webservice.port` will be ignored. ''', 'skip-old': 'No equivalent old-style setting', 'versionadded': '5.3.0', @@ -3252,35 +3252,12 @@ 'help' : 'Amount of logging in the webserver (none, normal, detailed)', 'doc' : ''' One of ``none``, ``normal``, ``detailed``. -The amount of logging the webserver must do. 'none' means no useful webserver information will be logged. -When set to 'normal', the webserver will log a line per request that should be familiar:: - - [webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 'GET /api/v1/servers/localhost/bla HTTP/1.1' 404 196 - -When set to 'detailed', all information about the request and response are logged:: - - [webserver] e235780e-a5cf-415e-9326-9d33383e739e Request Details: - [webserver] e235780e-a5cf-415e-9326-9d33383e739e Headers: - [webserver] e235780e-a5cf-415e-9326-9d33383e739e accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 - [webserver] e235780e-a5cf-415e-9326-9d33383e739e accept-encoding: gzip, deflate - [webserver] e235780e-a5cf-415e-9326-9d33383e739e accept-language: en-US,en;q=0.5 - [webserver] e235780e-a5cf-415e-9326-9d33383e739e connection: keep-alive - [webserver] e235780e-a5cf-415e-9326-9d33383e739e dnt: 1 - [webserver] e235780e-a5cf-415e-9326-9d33383e739e host: 127.0.0.1:8081 - [webserver] e235780e-a5cf-415e-9326-9d33383e739e upgrade-insecure-requests: 1 - [webserver] e235780e-a5cf-415e-9326-9d33383e739e user-agent: Mozilla/5.0 (X11; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0 - [webserver] e235780e-a5cf-415e-9326-9d33383e739e No body - [webserver] e235780e-a5cf-415e-9326-9d33383e739e Response details: - [webserver] e235780e-a5cf-415e-9326-9d33383e739e Headers: - [webserver] e235780e-a5cf-415e-9326-9d33383e739e Connection: close - [webserver] e235780e-a5cf-415e-9326-9d33383e739e Content-Length: 49 - [webserver] e235780e-a5cf-415e-9326-9d33383e739e Content-Type: text/html; charset=utf-8 - [webserver] e235780e-a5cf-415e-9326-9d33383e739e Server: PowerDNS/0.0.15896.0.gaba8bab3ab - [webserver] e235780e-a5cf-415e-9326-9d33383e739e Full body: - [webserver] e235780e-a5cf-415e-9326-9d33383e739e Not Found

Not Found

- [webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 'GET /api/v1/servers/localhost/bla HTTP/1.1' 404 196 - -The value between the hooks is a UUID that is generated for each request. This can be used to find all lines related to a single request. +The amount of logging the webserver must do. ``none`` means no useful webserver information will be logged. +When set to ``normal``, the webserver will log a line per request:: + + Feb 03 14:54:00 msg="Request" subsystem="webserver" level="0" prio="Notice" tid="0" ts="1738590840.208" HTTPVersion="HTTP/1.1" method="GET" remote="[::1]:49880" respsize="5418" status="200" uniqueid="a31a280d-29de-4db8-828f-edc862eb8653" urlpath="/" + +When set to ``detailed``, all available information about the request and response is logged. .. note:: The webserver logs these line on the NOTICE level. The :ref:`setting-loglevel` seting must be 5 or higher for these lines to end up in the log. From ebb4d066e7704cf13d00a5d67727eb3412e5910a Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Mon, 3 Feb 2025 16:02:11 +0100 Subject: [PATCH 31/38] Go back to ring and generate Cargo.lock with older version so it uses Cargo.lock version = 3 The default rusttls provider has quite some dependecies that need newer Rust. ring is much morer lenient. --- .../recursordist/rec-rust-lib/rust/Cargo.lock | 460 +++++------------- .../recursordist/rec-rust-lib/rust/Cargo.toml | 2 +- 2 files changed, 119 insertions(+), 343 deletions(-) diff --git a/pdns/recursordist/rec-rust-lib/rust/Cargo.lock b/pdns/recursordist/rec-rust-lib/rust/Cargo.lock index 311a4e801975..a7c6b91f0fd1 100644 --- a/pdns/recursordist/rec-rust-lib/rust/Cargo.lock +++ b/pdns/recursordist/rec-rust-lib/rust/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" @@ -18,44 +18,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] -name = "aho-corasick" -version = "1.1.3" +name = "anstyle" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anyhow" -version = "1.0.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" - -[[package]] -name = "aws-lc-rs" -version = "1.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b7ddaa2c56a367ad27a094ad8ef4faacf8a617c2575acb2ba88949df999ca" -dependencies = [ - "aws-lc-sys", - "paste", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.25.0" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b2ddd3ada61a305e1d8bb6c005d1eaa7d14d903681edfc400406d523a9b491" -dependencies = [ - "bindgen", - "cc", - "cmake", - "dunce", - "fs_extra", - "paste", -] +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "backtrace" @@ -78,61 +50,21 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", - "which", -] - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - [[package]] name = "bytes" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "cc" -version = "1.1.18" +version = "1.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" dependencies = [ - "jobserver", - "libc", "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -140,25 +72,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "clang-sys" -version = "1.8.1" +name = "clap" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ - "glob", - "libc", - "libloading", + "clap_builder", ] [[package]] -name = "cmake" -version = "0.1.53" +name = "clap_builder" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24a03c8b52922d68a1589ad61032f2c1aa5a8158d2aa0d93c6e9534944bbad6" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ - "cc", + "anstyle", + "clap_lex", + "strsim", ] +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -171,25 +109,26 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.128" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ccead7d199d584d139148b04b4a368d1ec7556a1d9ea2548febb1b9d49f9a4" +checksum = "0fc894913dccfed0f84106062c284fa021c3ba70cb1d78797d6f5165d4492e45" dependencies = [ "cc", + "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", + "foldhash", "link-cplusplus", ] [[package]] name = "cxx-build" -version = "1.0.128" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77953e99f01508f89f55c494bfa867171ef3a6c8cea03d26975368f2121a5c1" +checksum = "503b2bfb6b3e8ce7f95d865a67419451832083d3186958290cee6c53e39dfcfe" dependencies = [ "cc", "codespan-reporting", - "once_cell", "proc-macro2", "quote", "scratch", @@ -197,33 +136,35 @@ dependencies = [ ] [[package]] -name = "cxxbridge-flags" -version = "1.0.128" +name = "cxxbridge-cmd" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65777e06cc48f0cb0152024c77d6cf9e4bdb4408e7b48bea993d42fa0f5b02b6" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" +checksum = "e0d2cb64a95b4b5a381971482235c4db2e0208302a962acdbe314db03cbbe2fb" dependencies = [ + "clap", + "codespan-reporting", "proc-macro2", "quote", "syn", ] [[package]] -name = "dunce" -version = "1.0.5" +name = "cxxbridge-flags" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +checksum = "5f797b0206463c9c2a68ed605ab28892cca784f1ef066050f4942e3de26ad885" [[package]] -name = "either" -version = "1.13.0" +name = "cxxbridge-macro" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "e79010a2093848e65a3e0f7062d3f02fb2ef27f866416dfe436fccfa73d3bb59" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn", +] [[package]] name = "equivalent" @@ -231,22 +172,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -256,12 +193,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "futures-channel" version = "0.3.31" @@ -312,38 +243,17 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "glob" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -375,9 +285,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -387,9 +297,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -443,9 +353,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", @@ -453,61 +363,21 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" - -[[package]] -name = "itertools" -version = "0.12.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "lazycell" -version = "1.3.0" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "libc" -version = "0.2.162" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" - -[[package]] -name = "libloading" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" -dependencies = [ - "cfg-if", - "windows-targets", -] +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libyml" @@ -528,81 +398,46 @@ dependencies = [ "cc", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "paste" -version = "1.0.15" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "percent-encoding" @@ -612,9 +447,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -622,30 +457,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "prettyplease" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -675,35 +500,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "ring" version = "0.17.8" @@ -725,33 +521,14 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustix" -version = "0.38.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - [[package]] name = "rustls" -version = "0.23.21" +version = "0.23.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ - "aws-lc-rs", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -769,9 +546,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" @@ -779,17 +556,22 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "scratch" @@ -799,18 +581,18 @@ checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -846,9 +628,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -860,6 +642,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -868,9 +656,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.87" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -888,9 +676,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "libc", @@ -943,15 +731,15 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "untrusted" @@ -989,18 +777,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "winapi-util" version = "0.1.9" diff --git a/pdns/recursordist/rec-rust-lib/rust/Cargo.toml b/pdns/recursordist/rec-rust-lib/rust/Cargo.toml index 8de626588986..f48f3de8a256 100644 --- a/pdns/recursordist/rec-rust-lib/rust/Cargo.toml +++ b/pdns/recursordist/rec-rust-lib/rust/Cargo.toml @@ -22,7 +22,7 @@ hyper-util = { version = "0.1", features = ["tokio"]} bytes = "1.8" form_urlencoded = "1.2" hyper-rustls = { version = "0.27", default-features = false } -rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] } +rustls = { version = "0.23", default-features = false, features = ["ring"] } rustls-pemfile = "2.2" pki-types = { package = "rustls-pki-types", version = "1.10" } tokio-rustls = { version = "0.26", default-features = false } From 9da62ebf10821ace8dee73af8026631481a8ac25 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Tue, 4 Feb 2025 09:33:41 +0100 Subject: [PATCH 32/38] Nicer error message on private key read or decode failure --- pdns/recursordist/rec-rust-lib/rust/src/web.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pdns/recursordist/rec-rust-lib/rust/src/web.rs b/pdns/recursordist/rec-rust-lib/rust/src/web.rs index e9a394d5a4a3..d131b163aff9 100644 --- a/pdns/recursordist/rec-rust-lib/rust/src/web.rs +++ b/pdns/recursordist/rec-rust-lib/rust/src/web.rs @@ -959,7 +959,10 @@ pub fn serveweb( .spawn(move || { runtime.block_on(async { while let Some(res) = set.join_next().await { - let msg = format!("{:?}", res); + let msg = match res { + Ok(Err(wrapped)) => format!("{:?}", wrapped), + _ => format!("{:?}", res) + }; rustmisc::error( &ctx.logger, rustmisc::Priority::Error, @@ -1000,7 +1003,15 @@ fn load_private_key(filename: &str) -> std::io::Result Ok(pkey), + Ok(None) => Err( + std::io::Error::new( + std::io::ErrorKind::Other, + format!("failed to parse private key from {}", filename), + )), + Err(e) => Err(e) + } } // impl below needed because the classes are used in the Context, which gets passed around. From ab431fb092e74b172630824b64d04eb915755dc4 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Tue, 4 Feb 2025 09:48:24 +0100 Subject: [PATCH 33/38] Handle a few remaining remnants of POC code --- pdns/recursordist/rec-rust-lib/table.py | 4 ++-- pdns/recursordist/reczones.cc | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pdns/recursordist/rec-rust-lib/table.py b/pdns/recursordist/rec-rust-lib/table.py index 93600481b8eb..e07e176a65f3 100644 --- a/pdns/recursordist/rec-rust-lib/table.py +++ b/pdns/recursordist/rec-rust-lib/table.py @@ -3208,10 +3208,10 @@ 'section' : 'webservice', 'type' : LType.ListIncomingWSConfigs, 'default' : '', - 'help' : 'XXXX', + 'help' : 'IP addresses and associated attributes for the webserver to listen on', 'doc' : ''' IP addresses and associated attributes for the webserver to listen on. -If this setting has a non-default value, :ref:`setting-yaml-webservice.address` and :ref:`setting-yaml-webservice.port` will be ignored. +If this setting has a non-default value, :ref:`setting-yaml-webservice.address` and :ref:`setting-yaml-webservice.port` will be ignored. Note multiple listen addresses can be configured and https is supported as well, in contrast to earlier (pre 5.3.0) versions. ''', 'skip-old': 'No equivalent old-style setting', 'versionadded': '5.3.0', diff --git a/pdns/recursordist/reczones.cc b/pdns/recursordist/reczones.cc index b92d6daed690..d40df7a31f3b 100644 --- a/pdns/recursordist/reczones.cc +++ b/pdns/recursordist/reczones.cc @@ -34,11 +34,7 @@ #include "zoneparser-tng.hh" #include "rec-rust-lib/cxxsettings.hh" #include "rec-system-resolve.hh" - -// XXX consider including rec-main.hh? -extern int g_argc; -extern char** g_argv; -extern string g_yamlSettingsSuffix; +#include "rec-main.hh" bool primeHints(time_t now) { @@ -198,7 +194,6 @@ string reloadZoneConfiguration(bool yaml) oldAndNewDomains.insert(entry.first); } - extern LockGuarded> g_initialDomainMap; // XXX { auto lock = g_initialDomainMap.lock(); if (*lock) { @@ -221,7 +216,6 @@ string reloadZoneConfiguration(bool yaml) wipeCaches(entry, true, 0xffff); } *g_initialDomainMap.lock() = std::move(newDomainMap); - extern LockGuarded> g_initialAllowNotifyFor; // XXX *g_initialAllowNotifyFor.lock() = std::move(newNotifySet); return "ok\n"; } From 862b33b157d09992deb584db6da25da0ce9b61a2 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Tue, 4 Feb 2025 11:06:18 +0100 Subject: [PATCH 34/38] Process very initial review comments (docs and comments and trivial changes) --- .github/actions/spell-check/expect.txt | 2 -- pdns/recursordist/rec-main.cc | 17 ----------------- pdns/recursordist/rec-rust-lib/cxxsupport.cc | 2 -- .../rec-rust-lib/docs-new-preamble-in.rst | 4 ++-- pdns/recursordist/rec-rust-lib/rust/build.rs | 2 +- 5 files changed, 3 insertions(+), 24 deletions(-) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index ec17629a36f6..838f4ef91357 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -918,7 +918,6 @@ nproxy NPTL NSes NSID -nsid nsis nsrecord nsset @@ -1500,7 +1499,6 @@ Viala viewcode visitedlinkcolor Vixie -vixie vla Voegeli Volker diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index 0c2de94667e0..9718d44f9be7 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -2920,26 +2920,9 @@ static void recursorThread() } t_fdm = unique_ptr(getMultiplexer(log)); -#if 0 - std::unique_ptr rws; -#endif t_fdm->addReadFD(threadInfo.getPipes().readToThread, handlePipeRequest); if (threadInfo.isHandler()) { -#if 0 - if (::arg().mustDo("webserver")) { - SLOG(g_log << Logger::Warning << "Enabling web server" << endl, - log->info(Logr::Info, "Enabling web server")); - try { - rws = make_unique(t_fdm.get()); - } - catch (const PDNSException& e) { - SLOG(g_log << Logger::Error << "Unable to start the internal web server: " << e.reason << endl, - log->error(Logr::Critical, e.reason, "Exception while starting internal web server")); - _exit(99); - } - } -#endif SLOG(g_log << Logger::Info << "Enabled '" << t_fdm->getName() << "' multiplexer" << endl, log->info(Logr::Info, "Enabled multiplexer", "name", Logging::Loggable(t_fdm->getName()))); } diff --git a/pdns/recursordist/rec-rust-lib/cxxsupport.cc b/pdns/recursordist/rec-rust-lib/cxxsupport.cc index bbc7f60dd194..89845cb26ce3 100644 --- a/pdns/recursordist/rec-rust-lib/cxxsupport.cc +++ b/pdns/recursordist/rec-rust-lib/cxxsupport.cc @@ -1480,8 +1480,6 @@ bool isValidHostname(::rust::Str str) } } -void findBetterSolution(const std::unique_ptr& /* x */){}; - std::unique_ptr comboaddress(::rust::Str str) { return std::make_unique(::ComboAddress(std::string(str))); diff --git a/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst b/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst index 7723373f06ea..6802123ab0a3 100644 --- a/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst +++ b/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst @@ -537,7 +537,7 @@ For catalog zone members in a group, the forwarding parameters will be taken fro The forwarding definitions will be written into a file ``$api_dir/catzone.$zonename``. :ref:`setting-yaml-webservice.api_dir` must be defined, the directory must exist and be writable by the :program:`Recursor` process. IncomingWSConfig -^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^ As of version 5.3.0, an incoming web server configuration is defined as .. code-block:: yaml @@ -562,7 +562,7 @@ A :ref:`setting-yaml-webservice.listen` section contains a sequence of `Incoming If no ``tls`` section is present, plaintext ``http`` connections are accepted on the listed addresses. -If a ``tls`` section is present, clienst are required to use ``https`` to contact any of the address-port combinations listen in addresses. At the moment it is not possible to list additional properties of the TLS listener and encrypted key files cannot be used. +If a ``tls`` section is present, clients are required to use ``https`` to contact any of the address-port combinations listen in addresses. At the moment it is not possible to list additional properties of the TLS listener and encrypted key files cannot be used. The YAML settings diff --git a/pdns/recursordist/rec-rust-lib/rust/build.rs b/pdns/recursordist/rec-rust-lib/rust/build.rs index 21eb063a8d1a..b2e9b952eb2c 100644 --- a/pdns/recursordist/rec-rust-lib/rust/build.rs +++ b/pdns/recursordist/rec-rust-lib/rust/build.rs @@ -7,7 +7,7 @@ fn main() { .flag("-I../../..") .compile("settings"); - // lib.rs is genertated an take carte of by parent Makefile + // lib.rs is generated and take care of by parent Makefile println!("cargo:rerun-if-changed=src/misc.rs"); println!("cargo:rerun-if-changed=src/web.rs"); } From 41e5321b528868550db922c385e060fe11b6bf15 Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Wed, 5 Feb 2025 16:31:53 +0100 Subject: [PATCH 35/38] Sprinkle some comments and copyright notices --- .../rec-rust-lib/docs-new-preamble-in.rst | 2 +- .../rec-rust-lib/rust/src/misc.rs | 22 +++++++++++++ .../recursordist/rec-rust-lib/rust/src/web.rs | 33 +++++++++++++++---- pdns/recursordist/ws-recursor.cc | 13 +++++--- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst b/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst index 6802123ab0a3..7c14e5ed3a63 100644 --- a/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst +++ b/pdns/recursordist/rec-rust-lib/docs-new-preamble-in.rst @@ -545,7 +545,7 @@ As of version 5.3.0, an incoming web server configuration is defined as addresses: [] Sequence of SocketAddress tls: certificates: file containing full certificate chain in PEM format - key: file contaiing private key in PEM format + key: file containing private key in PEM format A :ref:`setting-yaml-webservice.listen` section contains a sequence of `IncomingWSConfig`_, for example: diff --git a/pdns/recursordist/rec-rust-lib/rust/src/misc.rs b/pdns/recursordist/rec-rust-lib/rust/src/misc.rs index f3999afa0abc..81f97694e605 100644 --- a/pdns/recursordist/rec-rust-lib/rust/src/misc.rs +++ b/pdns/recursordist/rec-rust-lib/rust/src/misc.rs @@ -1,3 +1,25 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #[cxx::bridge(namespace = "pdns::rust::misc")] pub mod rustmisc { diff --git a/pdns/recursordist/rec-rust-lib/rust/src/web.rs b/pdns/recursordist/rec-rust-lib/rust/src/web.rs index d131b163aff9..e4cb207651d4 100644 --- a/pdns/recursordist/rec-rust-lib/rust/src/web.rs +++ b/pdns/recursordist/rec-rust-lib/rust/src/web.rs @@ -1,10 +1,28 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + /* TODO - Table based routing? -- Authorization: metrics and plain files (and more?) are not subject to password auth plus the code needs a n careful audit. -- Code is now in settings dir. It's only possible to split the modules into separate Rust libs if we - use shared libs (in theory, I did not try). Currently all CXX using Rust cargo's must be compiled - as one and refer to a single static Rust runtime - Ripping out yahttp stuff, providing some basic classes only. ATM we do use a few yahttp include files (but no .cc) - Some classes (NetmaskGroup, ComboAddress) need a UniquePtr Wrapper to keep them opaque (iputils cannot be included without big headages in bridge.hh at the moment). We could seperate @@ -112,6 +130,7 @@ fn nonapi_wrapper( } } +#[allow(clippy::too_many_arguments)] fn file_wrapper( ctx: &Context, handler: FileFunc, @@ -139,6 +158,7 @@ fn file_wrapper( handler(ctx, method, path, request, response); } +#[allow(clippy::too_many_arguments)] fn api_wrapper( logger: &cxx::SharedPtr, ctx: &Context, @@ -322,7 +342,7 @@ type FileFunc = fn( response: &mut rustweb::Response, ); -// Match a request and return the function that imlements it, this should probably be table based. +// Match a request and return the function that implements it, this should probably be table based. fn matcher( method: &Method, path: &str, @@ -1023,6 +1043,7 @@ unsafe impl Send for rustmisc::Logger {} unsafe impl Sync for rustmisc::Logger {} #[cxx::bridge(namespace = "pdns::rust::web::rec")] +#[allow(clippy::needless_lifetimes)] // Needed to avoid clippy warning for Request mod rustweb { extern "C++" { type CredentialsHolder; @@ -1070,7 +1091,7 @@ mod rustweb { } // Clippy does not seem to understand what cxx does and complains about needless_lifetimes - // I was unable to silence that warning + // The warning is silenced that warning above struct Request<'a> { body: Vec, uri: String, diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 69969d50ecb6..d09286caa304 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -1033,6 +1033,9 @@ void serveRustWeb() else if (configLevel == "detailed") { loglevel = pdns::rust::misc::LogLevel::Detailed; } + // This function returns after having created the web server object that handles the requests. + // That object and its runtime are associated with a Posix thread that waits until all tasks are + // done, which normally never happens. See rec-rust-lib/rust/src/web.rs for details pdns::rust::web::rec::serveweb(config, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr), std::move(logPtr), loglevel); } @@ -1049,12 +1052,12 @@ static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Res } } -// Convert what we receive from Rust into C++ data, call funtions and convert results back to Rust data +// Convert what we receive from Rust into C++ data, call functions and convert results back to Rust data static void rustWrapper(const std::function& func, const pdns::rust::web::rec::Request& rustRequest, pdns::rust::web::rec::Response& rustResponse) { HttpRequest request; HttpResponse response; - request.body = std::string(reinterpret_cast(rustRequest.body.data()), rustRequest.body.size()); + request.body = std::string(reinterpret_cast(rustRequest.body.data()), rustRequest.body.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) request.url = std::string(rustRequest.uri); for (const auto& [key, value] : rustRequest.vars) { request.getvars[std::string(key)] = std::string(value); @@ -1062,8 +1065,10 @@ static void rustWrapper(const std::function& for (const auto& [key, value] : rustRequest.parameters) { request.parameters[std::string(key)] = std::string(value); } - request.d_slog = g_slog; // XXX - response.d_slog = g_slog; // XXX + // These two log objects are not used by the Rust code, as they take the logging object from the + // context, initalized from an argument to pdns::rust::web::rec::serveweb() + request.d_slog = g_slog; + response.d_slog = g_slog; try { func(&request, &response); } From 25c8beba49ae75faf3a0a3a79777b0b861fb8f1a Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Fri, 7 Feb 2025 11:06:37 +0100 Subject: [PATCH 36/38] Typos is comments Co-authored-by: Miod Vallat --- pdns/recursordist/rec-rust-lib/rust/src/web.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pdns/recursordist/rec-rust-lib/rust/src/web.rs b/pdns/recursordist/rec-rust-lib/rust/src/web.rs index e4cb207651d4..703698cfde61 100644 --- a/pdns/recursordist/rec-rust-lib/rust/src/web.rs +++ b/pdns/recursordist/rec-rust-lib/rust/src/web.rs @@ -25,7 +25,7 @@ TODO - Table based routing? - Ripping out yahttp stuff, providing some basic classes only. ATM we do use a few yahttp include files (but no .cc) - Some classes (NetmaskGroup, ComboAddress) need a UniquePtr Wrapper to keep them opaque (iputils - cannot be included without big headages in bridge.hh at the moment). We could seperate + cannot be included without big headaches in bridge.hh at the moment). We could seperate NetmaskGroup, but I expect ComboAddress to not work as it is union. - Avoid unsafe? Can it be done? */ @@ -935,7 +935,7 @@ pub fn serveweb( let tls = crate::web::rustweb::IncomingTLS { certificate: config.tls.certificate.clone(), key: config.tls.key.clone(), - // password: config.tls.password.clone(), not supported (yet), ruttls does not handle it + // password: config.tls.password.clone(), not supported (yet), rusttls does not handle it }; if !tls.certificate.is_empty() { tls_enabled = true; @@ -1091,7 +1091,7 @@ mod rustweb { } // Clippy does not seem to understand what cxx does and complains about needless_lifetimes - // The warning is silenced that warning above + // The warning is silenced above struct Request<'a> { body: Vec, uri: String, From 9ddb42276dcd980056f1c29dfd301dcf4a33e58c Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Wed, 12 Feb 2025 10:41:23 +0100 Subject: [PATCH 37/38] Apply suggestions from code review Co-authored-by: Remi Gacogne --- pdns/recursordist/rec-rust-lib/rust/build.rs | 2 +- pdns/recursordist/rec-rust-lib/rust/src/web.rs | 2 +- pdns/recursordist/ws-recursor.cc | 2 +- regression-tests.api/runtests.py | 2 +- regression-tests.api/test_helper.py | 2 +- regression-tests.recursor-dnssec/test_Prometheus.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pdns/recursordist/rec-rust-lib/rust/build.rs b/pdns/recursordist/rec-rust-lib/rust/build.rs index b2e9b952eb2c..c9a07d3ae04c 100644 --- a/pdns/recursordist/rec-rust-lib/rust/build.rs +++ b/pdns/recursordist/rec-rust-lib/rust/build.rs @@ -7,7 +7,7 @@ fn main() { .flag("-I../../..") .compile("settings"); - // lib.rs is generated and take care of by parent Makefile + // lib.rs is generated and taken care of by parent Makefile println!("cargo:rerun-if-changed=src/misc.rs"); println!("cargo:rerun-if-changed=src/web.rs"); } diff --git a/pdns/recursordist/rec-rust-lib/rust/src/web.rs b/pdns/recursordist/rec-rust-lib/rust/src/web.rs index 703698cfde61..aa0a7849fa2d 100644 --- a/pdns/recursordist/rec-rust-lib/rust/src/web.rs +++ b/pdns/recursordist/rec-rust-lib/rust/src/web.rs @@ -198,7 +198,7 @@ fn api_wrapper( if !auth_ok { for kv in &request.vars { cxx::let_cxx_string!(s = &kv.value); - if kv.key == "x-api-key" && ctx.api_ch.as_ref().unwrap().matches(&s) { + if kv.key == "api-key" && ctx.api_ch.as_ref().unwrap().matches(&s) { auth_ok = true; break; } diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index d09286caa304..17a9eb5a799d 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -1066,7 +1066,7 @@ static void rustWrapper(const std::function& request.parameters[std::string(key)] = std::string(value); } // These two log objects are not used by the Rust code, as they take the logging object from the - // context, initalized from an argument to pdns::rust::web::rec::serveweb() + // context, initialized from an argument to pdns::rust::web::rec::serveweb() request.d_slog = g_slog; response.d_slog = g_slog; try { diff --git a/regression-tests.api/runtests.py b/regression-tests.api/runtests.py index 99932570d3eb..b82c6237529c 100755 --- a/regression-tests.api/runtests.py +++ b/regression-tests.api/runtests.py @@ -284,7 +284,7 @@ def finalize_server(): if daemon == 'authoritative': res = requests.get('http://127.0.0.1:%s/' % WEBPORT) else: - res = requests.get('https://127.0.0.1:%s/' % WEBPORT, verify=False) + res = requests.get('https://127.0.0.1:%s/' % WEBPORT, verify='ca.pem') available = True break except HTTPError as http_err: diff --git a/regression-tests.api/test_helper.py b/regression-tests.api/test_helper.py index fe8272560fcb..62651b8a2f5a 100644 --- a/regression-tests.api/test_helper.py +++ b/regression-tests.api/test_helper.py @@ -40,7 +40,7 @@ def setUp(self): self.session.headers = {'X-API-Key': os.environ.get('APIKEY', 'changeme-key'), 'Origin': 'http://%s:%s' % (self.server_address, self.server_port)} if is_recursor(): self.server_url = 'https://%s:%s/' % (self.server_address, self.server_port) - self.session.verify = False + self.session.verify = 'ca.pem' def url(self, relative_url): return urljoin(self.server_url, relative_url) diff --git a/regression-tests.recursor-dnssec/test_Prometheus.py b/regression-tests.recursor-dnssec/test_Prometheus.py index 065c803e6d2e..45730987551d 100644 --- a/regression-tests.recursor-dnssec/test_Prometheus.py +++ b/regression-tests.recursor-dnssec/test_Prometheus.py @@ -109,7 +109,7 @@ def generateRecursorConfig(cls, confdir): def testPrometheus(self): self.waitForTCPSocket("127.0.0.1", self._wsPort) url = 'https://user:' + self._wsPassword + '@127.0.0.1:' + str(self._wsPort) + '/metrics' - r = requests.get(url, timeout=self._wsTimeout, verify=False) + r = requests.get(url, timeout=self._wsTimeout, verify='ca.pem') self.assertTrue(r) self.assertEqual(r.status_code, 200) self.checkPrometheusContentBasic(r.text) From 81a428a4a38dd295172e6334454ae1916abac61d Mon Sep 17 00:00:00 2001 From: Otto Moerbeek Date: Wed, 12 Feb 2025 10:51:55 +0100 Subject: [PATCH 38/38] Remove remains of unused PoC code --- .../recursordist/rec-rust-lib/rust/src/web.rs | 46 +++---------------- pdns/recursordist/ws-recursor.cc | 6 +-- 2 files changed, 7 insertions(+), 45 deletions(-) diff --git a/pdns/recursordist/rec-rust-lib/rust/src/web.rs b/pdns/recursordist/rec-rust-lib/rust/src/web.rs index aa0a7849fa2d..6445db01eac9 100644 --- a/pdns/recursordist/rec-rust-lib/rust/src/web.rs +++ b/pdns/recursordist/rec-rust-lib/rust/src/web.rs @@ -155,7 +155,7 @@ fn file_wrapper( unauthorized(response, headers, "Basic"); return; } - handler(ctx, method, path, request, response); + handler(method, path, request, response); } #[allow(clippy::too_many_arguments)] @@ -267,9 +267,8 @@ fn api_wrapper( } } -// Data used by requests handlers, only counter is r/w. +// Data used by requests handlers, if you add a r/w variable, make sure it's safe for concurrent access! struct Context { - urls: Vec, password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr, @@ -279,38 +278,11 @@ struct Context { // Serve a file fn file( - ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, response: &mut rustweb::Response, ) { - let mut uripath = path; - if uripath == "/" { - uripath = "/index.html"; - } - let pos = ctx - .urls - .iter() - .position(|x| String::from("/") + x == uripath); - if pos.is_none() { - rustmisc::log( - request.logger, - rustweb::Priority::Debug, - "not found", - &vec![ - rustmisc::KeyValue { - key: "method".to_string(), - value: method.to_string(), - }, - rustmisc::KeyValue { - key: "uripath".to_string(), - value: uripath.to_string(), - }, - ], - ); - } - // This calls into C++ if rustweb::serveStuff(request, response).is_err() { // Return 404 not found response. @@ -319,15 +291,15 @@ fn file( rustmisc::log( request.logger, rustweb::Priority::Debug, - "not found case 2", + "not found", &vec![ rustmisc::KeyValue { key: "method".to_string(), value: method.to_string(), }, rustmisc::KeyValue { - key: "uripath".to_string(), - value: uripath.to_string(), + key: "path".to_string(), + value: path.to_string(), }, ], ); @@ -335,7 +307,6 @@ fn file( } type FileFunc = fn( - ctx: &Context, method: &Method, path: &str, request: &rustweb::Request, @@ -889,7 +860,6 @@ async fn serveweb_async( pub fn serveweb( incoming: &Vec, - urls: &[String], password_ch: cxx::UniquePtr, api_ch: cxx::UniquePtr, acl: cxx::UniquePtr, @@ -898,7 +868,6 @@ pub fn serveweb( ) -> Result<(), std::io::Error> { // Context, atomically reference counted let ctx = Arc::new(Context { - urls: urls.to_vec(), password_ch, api_ch, acl, @@ -1049,8 +1018,6 @@ mod rustweb { type CredentialsHolder; #[namespace = "pdns::rust::misc"] type NetmaskGroup = crate::misc::rustmisc::NetmaskGroup; - //#[namespace = "pdns::rust::misc"] - //type ComboAddress = crate::misc::rustmisc::ComboAddress; #[namespace = "pdns::rust::misc"] type Priority = crate::misc::rustmisc::Priority; #[namespace = "pdns::rust::misc"] @@ -1073,10 +1040,9 @@ mod rustweb { * Functions callable from C++ */ extern "Rust" { - // The main entry point, This function will return, but will setup thread(s) to handle requests. + // The main entry point, This function will return, but will setup thread(s) and tokio runtime to handle requests. fn serveweb( incoming: &Vec, - urls: &[String], pwch: UniquePtr, apikeych: UniquePtr, acl: UniquePtr, diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 17a9eb5a799d..850bcb1954a1 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -1005,10 +1005,6 @@ void serveRustWeb() config.emplace_back(tmp); } - ::rust::Vec<::rust::String> urls; - for (const auto& [url, _] : g_urlmap) { - urls.emplace_back(url); - } auto passwordString = arg()["webserver-password"]; std::unique_ptr password; if (!passwordString.empty()) { @@ -1036,7 +1032,7 @@ void serveRustWeb() // This function returns after having created the web server object that handles the requests. // That object and its runtime are associated with a Posix thread that waits until all tasks are // done, which normally never happens. See rec-rust-lib/rust/src/web.rs for details - pdns::rust::web::rec::serveweb(config, ::rust::Slice{urls.data(), urls.size()}, std::move(password), std::move(apikey), std::move(aclPtr), std::move(logPtr), loglevel); + pdns::rust::web::rec::serveweb(config, std::move(password), std::move(apikey), std::move(aclPtr), std::move(logPtr), loglevel); } static void fromCxxToRust(const HttpResponse& cxxresp, pdns::rust::web::rec::Response& rustResponse)