From f3d9392d22faa22732a119f2131ab28aeaf8e4bc 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 forking This is one the redesign PR for aardvark-dns and netavark. Design proposes that forking will happen at aardvark-end instead of netavark and aardvark will verify if servers are up before parent goes away. Redesign proposal * Aardvark will invoke server on the child process by 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 | 71 ++++++++++++++++++++++++++++++++++++++------- src/dns/coredns.rs | 25 ++++++++++++++++ src/main.rs | 2 ++ src/server/serve.rs | 52 +++++++++++++++++++++++++++++++++ 6 files changed, 174 insertions(+), 10 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..f1940c79 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -1,7 +1,9 @@ //! Runs the aardvark dns server with provided config +use crate::config; use crate::server::serve; use clap::Parser; use log::debug; +use nix::unistd::{fork, ForkResult}; use std::io::Error; #[derive(Parser, Debug)] @@ -19,18 +21,67 @@ impl Run { port: u32, filter_search_domain: String, ) -> Result<(), Error> { - debug!( - "Setting up aardvark server with input directory as {:?}", - input_dir - ); + // fork and verify if server is running + // and exit parent + // setsid() ensures that there is no controlling terminal on the child process - 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), - )); + match unsafe { fork() } { + Ok(ForkResult::Parent { child, .. }) => { + log::debug!("starting aardvark on a child with pid {}", child); + // verify aardvark here and block till all the ip are ready + match config::parse_configs(&input_dir) { + Ok((_, listen_ip_v4, listen_ip_v6)) => { + for (_, ip_list) in listen_ip_v4 { + for ip in ip_list { + serve::wait_till_aardvark_server_ready( + std::net::IpAddr::V4(ip), + port, + ); + } + } + for (_, ip_list) in listen_ip_v6 { + for ip in ip_list { + serve::wait_till_aardvark_server_ready( + std::net::IpAddr::V6(ip), + port, + ); + } + } + } + Err(e) => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("unable to parse config: {}", e), + )) + } + } + + Ok(()) + } + Ok(ForkResult::Child) => { + // remove any controlling terminals + // but don't hardstop if this fails + let _ = unsafe { libc::setsid() }; // check https://docs.rs/libc + 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), + )); + } + 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/dns/coredns.rs b/src/dns/coredns.rs index 0eb922af..e9e79371 100644 --- a/src/dns/coredns.rs +++ b/src/dns/coredns.rs @@ -6,6 +6,7 @@ use resolv_conf; use std::env; use std::fs::File; use std::io::Read; +use std::net::Ipv4Addr; use std::net::{IpAddr, SocketAddr}; use std::sync::{Arc, Mutex}; use tokio::net::UdpSocket; @@ -18,6 +19,8 @@ use trust_dns_proto::{ BufStreamHandle, }; +pub const AARDVARK_INTERNAL_HEALTHCHECK: &str = "aardvark-internal-healthcheck."; + pub struct CoreDns { name: Name, // name or origin network_name: String, // raw network name @@ -200,6 +203,28 @@ impl CoreDns { }; } + // If internal health check return early + if name.as_str() == AARDVARK_INTERNAL_HEALTHCHECK { + let record_name: Name = match Name::from_str_relaxed(name.as_str()) { + Ok(name) => name, + Err(e) => { + error!("Error while parsing record name: {:?}", e); + continue; + } + }; + req.add_answer( + Record::new() + .set_name(record_name.clone()) + .set_ttl(86400) + .set_rr_type(RecordType::A) + .set_dns_class(DNSClass::IN) + .set_rdata(RData::A(Ipv4Addr::new(127, 0, 0, 1))) + .clone(), + ); + reply(sender, src_address, &req); + continue; + } + // attempt intra network resolution match self.backend.lookup(&src_address.ip(), name.as_str()) { // If we go success from backend lookup diff --git a/src/main.rs b/src/main.rs index 52261338..92d29792 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,7 @@ enum SubCommand { } fn main() { + env_logger::builder().format_timestamp(None).init(); let formatter = Formatter3164 { facility: Facility::LOG_USER, hostname: None, @@ -71,6 +72,7 @@ fn main() { let filter_search_domain = opts .filter_search_domain .unwrap_or_else(|| String::from(".dns.podman")); + let result = match opts.subcmd { SubCommand::Run(run) => run.exec(dir, port, filter_search_domain), SubCommand::Version(version) => version.exec(), diff --git a/src/server/serve.rs b/src/server/serve.rs index ce1203c5..a6ff08f5 100644 --- a/src/server/serve.rs +++ b/src/server/serve.rs @@ -2,6 +2,7 @@ use crate::backend::DNSBackend; use crate::config; use crate::config::constants::AARDVARK_PID_FILE; use crate::dns::coredns::CoreDns; +use crate::dns::coredns::AARDVARK_INTERNAL_HEALTHCHECK; use log::{debug, error, info}; use signal_hook::consts::signal::SIGHUP; use signal_hook::iterator::Signals; @@ -17,6 +18,14 @@ use std::io::prelude::*; use std::path::Path; use std::process; +use std::str::FromStr; +use std::time::Duration; +use trust_dns_client::client::{Client, SyncClient}; +use trust_dns_client::rr::{DNSClass, Name, RData, Record, RecordType}; +use trust_dns_client::udp::UdpClientConnection; + +use std::time; + // Will be only used by server to share backend // across threads #[derive(Clone)] @@ -251,3 +260,46 @@ async fn send_broadcast(tx: &async_broadcast::Sender) { error!("unable to broadcast to child threads: {:?}", e); } } + +// verify_aardvark_server: is a public function to verify if aardvark server is running on a given address. +pub fn verify_aardvark_server(address_string: String) -> bool { + if let Ok(address) = address_string.parse() { + if let Ok(conn) = UdpClientConnection::with_timeout(address, Duration::from_millis(5)) { + // and then create the Client + let client = SyncClient::new(conn); + // server will be killed by last request + if let Ok(name) = Name::from_str(AARDVARK_INTERNAL_HEALTHCHECK) { + let response = client.query(&name, DNSClass::IN, RecordType::A); + if let Ok(response) = response { + let answers: &[Record] = response.answers(); + if let &RData::A(ref ip) = answers[0].rdata() { + // internal healthcheck is hardcoded to return v4 + // so this is fine + return *ip == Ipv4Addr::new(127, 0, 0, 1); + } + } + } + } + } + return false; +} + +// wait_till_aardvark_server_ready: is a public function which waits till aardvark server becomes healthy for +// a given ip address and port, retries for 10 times over a delay of 10ms. +pub fn wait_till_aardvark_server_ready(ip: IpAddr, port: u32) { + let address_string = format!("{}:{}", ip, port); + log::debug!("Verifying server on {}", address_string); + let mut verified = false; + let mut retry_count = 10; + while !verified && retry_count > 0 { + verified = verify_aardvark_server(address_string.clone()); + if verified { + // verification was successful + // dont retry anymore and return. + return; + } + let duration_millis = time::Duration::from_millis(500); + thread::sleep(duration_millis); + retry_count -= 1; + } +}