From 780f2817d8429237404c531cdeab74f5e834f78a Mon Sep 17 00:00:00 2001 From: Aditya R Date: Tue, 14 Jun 2022 15:12:21 +0530 Subject: [PATCH] main: run aardvark as a daemon via double-forking This is one the redesign PR for aardvark-dns and netavark. Design proposes that double-forking will happen at aardvark-end instead of netavark. Redesign proposal * Aardvark will invoke server on the child process by double-forking. * Parent waits for child to show up and verify against a dummy DNS query to check if server is running. * Exit parent on success and deatch child. * Calling process will wait for aardvark's parent process to return. * On successful return from parent it will be assumed that aardvark is running properly One new design is implemented and merged and netavark starts using this it should close * https://github.com/containers/podman/issues/14173 * https://github.com/containers/podman/issues/14171 Signed-off-by: Aditya R --- Cargo.lock | 31 +++++++++++ Cargo.toml | 3 + src/commands/run.rs | 132 ++++++++++++++++++++++++++++++++++++++++---- src/main.rs | 40 +++++++++----- 4 files changed, 181 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e13a4d7b..719e53ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,8 +10,11 @@ dependencies = [ "async-broadcast", "chrono", "clap", + "env_logger", "futures-util", + "libc", "log", + "nix", "resolv-conf", "signal-hook", "syslog", @@ -88,6 +91,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + [[package]] name = "cfg-if" version = "1.0.0" @@ -429,6 +438,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "mio" version = "0.8.2" @@ -461,6 +479,19 @@ dependencies = [ "smallvec", ] +[[package]] +name = "nix" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "ntapi" version = "0.3.7" diff --git a/Cargo.toml b/Cargo.toml index 690ac868..85cee40e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ exclude = ["/.cirrus.yml", "/.github/*"] clap = { version = "3.1.18", features = ["derive"] } syslog = "^6.0" log = "0.4.17" +env_logger = "0.9.0" trust-dns-server = "0.21.2" trust-dns-proto = "0.20.4" trust-dns-client = "0.20.4" @@ -21,6 +22,8 @@ signal-hook = "0.3.13" tokio = { version = "1.19.2", features = ["tokio-macros", "full"] } async-broadcast = "0.4.0" resolv-conf = "0.7.0" +nix = "0.23.0" +libc = "0.2" [build-dependencies] chrono = "*" diff --git a/src/commands/run.rs b/src/commands/run.rs index 7f284372..448cff03 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -2,7 +2,19 @@ use crate::server::serve; use clap::Parser; use log::debug; +use log::log_enabled; +use nix::{ + sys::wait::waitpid, + unistd::{fork, ForkResult}, +}; +use std::fs; use std::io::Error; +use std::path::Path; +use std::process::Command; +use std::process::Stdio; + +const SYSTEMD_CHECK_PATH: &str = "/run/systemd/system"; +const SYSTEMD_RUN: &str = "systemd-run"; #[derive(Parser, Debug)] pub struct Run {} @@ -13,24 +25,122 @@ impl Run { Self {} } + fn is_executable_in_path(program: &str) -> bool { + if let Ok(path) = std::env::var("PATH") { + for p in path.split(':') { + let p_str = format!("{}/{}", p, program); + if fs::metadata(p_str).is_ok() { + return true; + } + } + } + false + } + pub fn exec( &self, input_dir: String, port: u32, filter_search_domain: String, + serve: bool, + rootless: bool, ) -> Result<(), Error> { - debug!( - "Setting up aardvark server with input directory as {:?}", - input_dir - ); - - if let Err(er) = serve::serve(&input_dir, port, &filter_search_domain) { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!("Error starting server {}", er), - )); + let current_binary_path = match std::env::current_exe() { + Ok(path) => path.to_str().unwrap().to_string(), + Err(e) => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("failed to get current binary path: {}", e), + )); + } + }; + + if serve { + debug!( + "Setting up aardvark server with input directory as {:?}", + input_dir + ); + + println!("current binary path{:?}", std::env::current_exe()); + + if let Err(er) = serve::serve(&input_dir, port, &filter_search_domain) { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Error starting server {}", er), + )); + } + Ok(()) + } else { + // double fork and verify if server is running + // and exit parent + // Why double fork ? + // Its important that nature of aardvark server is more like a daemon + // so following block ensures that process returns when aardvark server + // is ready to serve + // + // setsid() ensures that there is no controlling terminal on the child process + + match unsafe { fork() } { + Ok(ForkResult::Parent { child, .. }) => { + log::debug!("starting aardvark on a child with pid {}", child); + if let Err(err) = waitpid(Some(child), None) { + log::debug!("error while waiting for child pid {}", err); + } + // verify aardvark here + Ok(()) + } + Ok(ForkResult::Child) => { + let mut aardvark_args = vec![]; + let binary_path = current_binary_path.clone(); + let port_cloned = port.clone().to_string(); + // only use systemd when it is booted, see sd_booted(3) + if Path::new(SYSTEMD_CHECK_PATH).exists() + && Run::is_executable_in_path(SYSTEMD_RUN) + { + aardvark_args = vec![SYSTEMD_RUN, "-q", "--scope"]; + + if rootless { + aardvark_args.push("--user"); + } + } + + aardvark_args.extend(vec![ + binary_path.as_str(), + "--config", + &input_dir, + "-p", + port_cloned.as_str(), + "--serve", + "true", + "run", + ]); + + log::debug!("start aardvark-dns: {:?}", aardvark_args); + + let output = match log_enabled!(log::Level::Debug) { + true => Stdio::inherit(), + false => Stdio::null(), + }; + + Command::new(&aardvark_args[0]) + .args(&aardvark_args[1..]) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(output) + // set RUST_LOG for aardvark + .env("RUST_LOG", log::max_level().as_str()) + .spawn()?; + Ok(()) + } + Err(err) => { + log::debug!("fork failed with error {}", err); + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("fork failed with error: {}", err), + )); + } + } } - Ok(()) } } diff --git a/src/main.rs b/src/main.rs index 52261338..8cbb8ebf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,12 @@ use syslog::{BasicLogger, Facility, Formatter3164}; #[derive(Parser, Debug)] #[clap(version = env!("CARGO_PKG_VERSION"))] struct Opts { + /// Tells if aardvark is being invoked from rootless environment + #[clap(short, long)] + rootless: Option, + /// Flag which tells aardvark to actuall start serving instead of forking + #[clap(short, long)] + serve: Option, /// Path to configuration directory #[clap(short, long)] config: Option, @@ -33,6 +39,7 @@ enum SubCommand { } fn main() { + env_logger::builder().format_timestamp(None).init(); let formatter = Formatter3164 { facility: Facility::LOG_USER, hostname: None, @@ -51,28 +58,33 @@ fn main() { Err(_) => Level::Info, }; - match syslog::unix(formatter) { - Ok(logger) => { - if let Err(e) = log::set_boxed_logger(Box::new(BasicLogger::new(logger))) - .map(|()| log::set_max_level(log_level.to_level_filter())) - { - eprintln!("failed to initialize syslog logger: {}", e) - }; - } - Err(e) => { - eprintln!("failed to connect to syslog: {}", e); - } - } - let opts = Opts::parse(); let dir = opts.config.unwrap_or_else(|| String::from("/dev/stdin")); let port = opts.port.unwrap_or(5533_u32); + let serve = opts.serve.unwrap_or(false); + let rootless = opts.rootless.unwrap_or(false); let filter_search_domain = opts .filter_search_domain .unwrap_or_else(|| String::from(".dns.podman")); + + if serve { + match syslog::unix(formatter) { + Ok(logger) => { + if let Err(e) = log::set_boxed_logger(Box::new(BasicLogger::new(logger))) + .map(|()| log::set_max_level(log_level.to_level_filter())) + { + eprintln!("failed to initialize syslog logger: {}", e) + }; + } + Err(e) => { + eprintln!("failed to connect to syslog: {}", e); + } + } + } + let result = match opts.subcmd { - SubCommand::Run(run) => run.exec(dir, port, filter_search_domain), + SubCommand::Run(run) => run.exec(dir, port, filter_search_domain, serve, rootless), SubCommand::Version(version) => version.exec(), };