]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Initial code for embedded web service in Rust
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 12 Nov 2024 07:57:22 +0000 (08:57 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 11 Feb 2025 15:28:21 +0000 (16:28 +0100)
13 files changed:
pdns/recursordist/rec-main.cc
pdns/recursordist/rec_control.cc
pdns/recursordist/reczones.cc
pdns/recursordist/settings/cxxsupport.cc
pdns/recursordist/settings/rust-preamble-in.rs
pdns/recursordist/settings/rust/Cargo.lock
pdns/recursordist/settings/rust/Cargo.toml
pdns/recursordist/settings/rust/Makefile.am
pdns/recursordist/settings/rust/build.rs
pdns/recursordist/settings/rust/src/bridge.hh
pdns/recursordist/settings/rust/src/web.rs [new file with mode: 0644]
pdns/recursordist/test-syncres_cc.cc
pdns/recursordist/ws-recursor.cc

index 2c0f123617b89135327a1ef1b480b7d63b1a7305..b25481c64eb2642dbf7b5f8192a4b38d6996e83b 100644 (file)
@@ -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<RecursorPacketCache>(g_maxPacketCacheEntries, ::arg().asNum("packetcache-shards"));
     }
 
+    extern void serveRustWeb();
+    serveRustWeb();
     ret = serviceMain(startupLog);
   }
   catch (const PDNSException& ae) {
index 2197ca0ba0eed7b1807f330a4849bfe26d777f9d..3e1a935704fdec27f5503c28f79d86ce1b442c54 100644 (file)
@@ -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 */)
+{
+}
index ff3fbeeabda3e0f98c1f2ed599d5da9052d027e2..f03ea750746a83b7795bfd7dc2e867f07f1424ef 100644 (file)
@@ -217,6 +217,8 @@ string reloadZoneConfiguration(bool yaml)
     for (const auto& entry : oldAndNewDomains) {
       wipeCaches(entry, true, 0xffff);
     }
+    extern std::shared_ptr<SyncRes::domainmap_t> g_initialDomainMap; // XXX
+    g_initialDomainMap = newDomainMap;
     return "ok\n";
   }
   catch (const std::exception& e) {
index 4c4fd13c1a339a7ee3856c62dd9a3213e806d651..28bb3423f8099abb24c50757315da9263f3c9e5b 100644 (file)
@@ -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;
   }
 }
+
index 64fef08e6782561ea340c8881d411b8fec25ce2e..ee611f1533ef94190470cca1f9e327c1ecf131a3 100644 (file)
@@ -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 {}
index 807b4931c5e96b9918731fbaab41f3bc48afcc40..6dc7ce9f48559c614ae08da30991308198c6541a 100644 (file)
@@ -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]]
index fed8e823ad400deeb7cc0f1fbb2a16573abbff69..6765cbb99c08bcdf17302a47c6a00c8c761c8ee4 100644 (file)
@@ -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"
index 5d0f63741cc2e5b350862c469e1090a68711f752..ae99af21eb1b2804d0934003d2ffac7325c1678f 100644 (file)
@@ -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
index ddddf7f2928e929713f536f268afa3c3eb143f51..e0fdf17c9c530b41430a09fd3f49e4cc2f36b03f 100644 (file)
@@ -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")
index 6a6fd2b41d77ffdd6dbe29bb27575af459c398aa..eca7324d9fa3bca6f8c219a92c9c5f15d5b83d72 100644 (file)
@@ -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 (file)
index 0000000..06dfa0d
--- /dev/null
@@ -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<dyn std::error::Error + Send + Sync>;
+type MyResult<T> = std::result::Result<T, GenericError>;
+type BoxBody = http_body_util::combinators::BoxBody<Bytes, hyper::Error>;
+
+static NOTFOUND: &[u8] = b"Not Found";
+
+fn full<T: Into<Bytes>>(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<IncomingBody>, urls: &Vec<String>) -> MyResult<Response<BoxBody>> {
+    let mut rust_response = Response::builder();
+    let mut vars: Vec<rustweb::KeyValue> = 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<String>) -> 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<String>, urls: &'static Vec<String>) -> 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<String>, urls: &'static Vec<String>) -> Result<()>;
+    }
+
+    struct KeyValue
+    {
+        key: String,
+        value: String,
+    }
+
+    struct Request
+    {
+        body: Vec<u8>,
+        uri: String,
+        vars: Vec<KeyValue>,
+    }
+
+    struct Response
+    {
+        status: u16,
+        body: Vec<u8>,
+        headers: Vec<KeyValue>,
+    }
+
+    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<()>;
+    }
+
+}
index 5c594122ef30817f9408f705b460e46baeb08e8a..a6447f230baad55188a4f5c50fea4e4ad09d4a2e 100644 (file)
@@ -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 */)
+{
+}
+
index 8e548ecdc413ac0a1c651317d4d5a8f5248a3fbc..0d8f5137a3a6d3ff7c02f344129fc24e4308f9f3 100644 (file)
@@ -25,6 +25,7 @@
 #include "ws-recursor.hh"
 #include "json.hh"
 
+#include <algorithm>
 #include <string>
 #include "namespaces.hh"
 #include <iostream>
 #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<string, string>& 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<const char*>(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<std::string, MetricDefinition> 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>& 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);
+}