Skip to content

Commit

Permalink
main: run aardvark as a daemon via 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  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

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

Signed-off-by: Aditya R <arajan@redhat.com>
  • Loading branch information
flouthoc committed Jun 15, 2022
1 parent 28786cc commit 7b8dd89
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 11 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 = "*"
92 changes: 81 additions & 11 deletions src/commands/run.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
//! Runs the aardvark dns server with provided config
use crate::config;
use crate::server::serve;
use clap::Parser;
use log::debug;
use nix::unistd::{dup2, fork, ForkResult};
use std::fs::OpenOptions;
use std::io::Error;
use std::os::unix::io::AsRawFd;

#[derive(Parser, Debug)]
pub struct Run {}
Expand All @@ -19,18 +22,85 @@ 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
// close fds -> stdout, stdin and stderr
let _ = unsafe { libc::close(0) };
let _ = unsafe { libc::close(1) };
let _ = unsafe { libc::close(2) };
let dev_null = Some(
OpenOptions::new()
.read(true)
.write(true)
.open("/dev/null")
.map_err(|e| std::io::Error::new(e.kind(), format!("/dev/null: {}", e)))?,
);
// redirect stdout, stdin and stderr to /dev/null
if let Some(dev_null) = dev_null {
let fd = dev_null.as_raw_fd();
let _ = dup2(fd, 0);
let _ = dup2(fd, 1);
let _ = dup2(fd, 2);
if fd < 2 {
std::mem::forget(dev_null);
}
}

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(())
}
}

Expand Down
25 changes: 25 additions & 0 deletions src/dns/coredns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ enum SubCommand {
}

fn main() {
env_logger::builder().format_timestamp(None).init();
let formatter = Formatter3164 {
facility: Facility::LOG_USER,
hostname: None,
Expand Down Expand Up @@ -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(),
Expand Down
52 changes: 52 additions & 0 deletions src/server/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)]
Expand Down Expand Up @@ -251,3 +260,46 @@ async fn send_broadcast(tx: &async_broadcast::Sender<bool>) {
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);
}
}
}
}
}
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;
}
}

0 comments on commit 7b8dd89

Please sign in to comment.