Skip to content

Commit

Permalink
main: run aardvark as a daemon via double-forking
Browse files Browse the repository at this point in the history
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

* containers/podman#14173
* containers/podman#14171

Signed-off-by: Aditya R <arajan@redhat.com>
  • Loading branch information
flouthoc committed Jun 14, 2022
1 parent 28786cc commit 780f281
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 25 deletions.
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 = "*"
132 changes: 121 additions & 11 deletions src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand All @@ -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(())
}
}

Expand Down
40 changes: 26 additions & 14 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>,
/// Flag which tells aardvark to actuall start serving instead of forking
#[clap(short, long)]
serve: Option<bool>,
/// Path to configuration directory
#[clap(short, long)]
config: Option<String>,
Expand All @@ -33,6 +39,7 @@ enum SubCommand {
}

fn main() {
env_logger::builder().format_timestamp(None).init();
let formatter = Formatter3164 {
facility: Facility::LOG_USER,
hostname: None,
Expand All @@ -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(),
};

Expand Down

0 comments on commit 780f281

Please sign in to comment.