From: Otto Moerbeek Date: Mon, 9 Dec 2024 15:07:53 +0000 (+0100) Subject: AlLow multiple listen addresses in config X-Git-Tag: dnsdist-2.0.0-alpha1~95^2~21 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3237ebc9e1048809d82d7e4806953304f7124f9b;p=thirdparty%2Fpdns.git AlLow multiple listen addresses in config --- diff --git a/pdns/recursordist/rec-main.hh b/pdns/recursordist/rec-main.hh index b1946b63e1..4fc3e3a6fb 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 96dc17ec57..50f3635d95 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 3c2a5a82ab..06c65a985c 100644 --- a/pdns/recursordist/settings/table.py +++ b/pdns/recursordist/settings/table.py @@ -3203,6 +3203,19 @@ Start the webserver (for REST API). 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 3181564100..37a1ade4ca 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;