From: Otto Moerbeek Date: Tue, 12 Nov 2024 07:57:22 +0000 (+0100) Subject: Initial code for embedded web service in Rust X-Git-Tag: dnsdist-2.0.0-alpha1~95^2~37 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=667189e7812f806d82c59bd19723422b50568c3e;p=thirdparty%2Fpdns.git Initial code for embedded web service in Rust --- diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index 2c0f123617..b25481c64e 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 2197ca0ba0..3e1a935704 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 ff3fbeeabd..f03ea75074 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 4c4fd13c1a..28bb3423f8 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 64fef08e67..ee611f1533 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 807b4931c5..6dc7ce9f48 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 fed8e823ad..6765cbb99c 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 5d0f63741c..ae99af21eb 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 ddddf7f292..e0fdf17c9c 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 6a6fd2b41d..eca7324d9f 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 0000000000..06dfa0de11 --- /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 5c594122ef..a6447f230b 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 8e548ecdc4..0d8f5137a3 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); +}