diff options
author | openshift-merge-bot[bot] <148852131+openshift-merge-bot[bot]@users.noreply.github.com> | 2024-07-29 14:28:10 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-29 14:28:10 +0000 |
commit | 0db33bb407fb3a56c87158a3f5d4367e46d34309 (patch) | |
tree | 7a955d7f48cee025940fe0ed41e53eec445a5af0 | |
parent | 4ee5d6b23722faf7094926242a35c4417b02f447 (diff) | |
parent | 7a5e6e7f82688c2a796d04151191fbf83fb33900 (diff) |
Merge pull request #484 from Luap99/tcp
add tcp support
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/commands/run.rs | 2 | ||||
-rw-r--r-- | src/config/mod.rs | 35 | ||||
-rw-r--r-- | src/dns/coredns.rs | 93 | ||||
-rw-r--r-- | src/error.rs | 118 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/main.rs | 4 | ||||
-rw-r--r-- | src/server/serve.rs | 119 | ||||
-rw-r--r-- | src/test/test.rs | 14 | ||||
-rw-r--r-- | test/100-basic-name-resolution.bats | 16 | ||||
-rw-r--r-- | test/600-errors.bats | 47 | ||||
-rw-r--r-- | test/helpers.bash | 24 |
13 files changed, 371 insertions, 112 deletions
@@ -6,7 +6,6 @@ version = 3 name = "aardvark-dns" version = "1.12.0-dev" dependencies = [ - "anyhow", "arc-swap", "chrono", "clap", @@ -103,12 +102,6 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] name = "arc-swap" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -26,9 +26,8 @@ clap = { version = "~4.4.10", features = ["derive"] } syslog = "^7.0.0" log = "0.4.22" hickory-server = "0.24.1" -hickory-proto = "0.24.1" +hickory-proto = { version = "0.24.1", features = ["tokio-runtime"] } hickory-client = "0.24.1" -anyhow = "1.0.86" futures-util = { version = "0.3.30", default-features = false } tokio = { version = "1.39.2", features = ["macros", "rt-multi-thread", "net", "signal"] } resolv-conf = "0.7.0" diff --git a/src/commands/run.rs b/src/commands/run.rs index b3becef..22db468 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -18,7 +18,7 @@ impl Run { pub fn exec( &self, input_dir: String, - port: u32, + port: u16, filter_search_domain: String, ) -> Result<(), Error> { // create a temporary path for unix socket diff --git a/src/config/mod.rs b/src/config/mod.rs index 88a097a..c49b344 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,4 +1,5 @@ use crate::backend::DNSBackend; +use crate::error::{AardvarkError, AardvarkResult}; use log::warn; use std::collections::HashMap; use std::fs::{metadata, read_dir, read_to_string}; @@ -22,19 +23,16 @@ pub mod constants; pub fn parse_configs( dir: &str, filter_search_domain: &str, -) -> Result< - ( - DNSBackend, - HashMap<String, Vec<Ipv4Addr>>, - HashMap<String, Vec<Ipv6Addr>>, - ), - std::io::Error, -> { +) -> AardvarkResult<( + DNSBackend, + HashMap<String, Vec<Ipv4Addr>>, + HashMap<String, Vec<Ipv6Addr>>, +)> { if !metadata(dir)?.is_dir() { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!("config directory {} must exist and be a directory", dir), - )); + return Err(AardvarkError::msg(format!( + "config directory {} must exist and be a directory", + dir + ))); } let mut network_membership: HashMap<String, Vec<String>> = HashMap::new(); @@ -82,13 +80,11 @@ pub fn parse_configs( } name_full.strip_suffix(constants::INTERNAL_SUFFIX).unwrap_or(&name_full).to_string() }, - None => return Err(std::io::Error::new( - std::io::ErrorKind::Other, + None => return Err(AardvarkError::msg( format!("configuration file {} name has non-UTF8 characters", s.to_string_lossy()), )), }, - None => return Err(std::io::Error::new( - std::io::ErrorKind::Other, + None => return Err(AardvarkError::msg( format!("configuration file {} does not have a file name, cannot identify network name", cfg.path().to_string_lossy()), )), }; @@ -191,13 +187,10 @@ pub fn parse_configs( } } None => { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!( + return Err(AardvarkError::msg(format!( "Container ID {} has an entry in IPs table, but not network membership table", ctr_id - ), - )) + ))) } } } diff --git a/src/dns/coredns.rs b/src/dns/coredns.rs index fab8677..b3bbc89 100644 --- a/src/dns/coredns.rs +++ b/src/dns/coredns.rs @@ -1,13 +1,17 @@ use crate::backend::DNSBackend; use crate::backend::DNSResult; +use crate::error::AardvarkResult; use arc_swap::ArcSwap; use arc_swap::Guard; use futures_util::StreamExt; use futures_util::TryStreamExt; use hickory_client::{client::AsyncClient, proto::xfer::SerialMessage, rr::rdata, rr::Name}; +use hickory_proto::tcp::TcpClientStream; use hickory_proto::{ + iocompat::AsyncIoTokioAsStd, op::{Message, MessageType, ResponseCode}, rr::{DNSClass, RData, Record, RecordType}, + tcp::TcpStream, udp::{UdpClientStream, UdpStream}, xfer::{dns_handle::DnsHandle, BufDnsStreamHandle, DnsRequest}, DnsStreamHandle, @@ -15,10 +19,10 @@ use hickory_proto::{ use log::{debug, error, trace, warn}; use resolv_conf; use resolv_conf::ScopedIp; -use std::convert::TryInto; use std::io::Error; use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; +use tokio::net::TcpListener; use tokio::net::UdpSocket; // Containers can be recreated with different ips quickly so @@ -29,20 +33,21 @@ const CONTAINER_TTL: u32 = 60; pub struct CoreDns { network_name: String, // raw network name - address: IpAddr, // server address - port: u32, // server port backend: &'static ArcSwap<DNSBackend>, // server's data store rx: flume::Receiver<()>, // kill switch receiver no_proxy: bool, // do not forward to external resolvers nameservers: Vec<ScopedIp>, // host nameservers from resolv.conf } +enum Protocol { + Udp, + Tcp, +} + impl CoreDns { // Most of the arg can be removed in design refactor. // so dont create a struct for this now. pub fn new( - address: IpAddr, - port: u32, network_name: String, backend: &'static ArcSwap<DNSBackend>, rx: flume::Receiver<()>, @@ -51,8 +56,6 @@ impl CoreDns { ) -> Self { CoreDns { network_name, - address, - port, backend, rx, no_proxy, @@ -60,18 +63,13 @@ impl CoreDns { } } - pub async fn run(&mut self) -> anyhow::Result<()> { - self.register_port().await - } - - // registers port supports udp for now - async fn register_port(&mut self) -> anyhow::Result<()> { - debug!("Starting listen on udp {:?}:{}", self.address, self.port); - - // Do we need to serve on tcp anywhere in future ? - let socket = UdpSocket::bind(format!("{}:{}", self.address, self.port)).await?; - let address = SocketAddr::new(self.address, self.port.try_into().unwrap()); - let (mut receiver, sender_original) = UdpStream::with_bound(socket, address); + pub async fn run( + &self, + udp_socket: UdpSocket, + tcp_listener: TcpListener, + ) -> AardvarkResult<()> { + let address = udp_socket.local_addr()?; + let (mut receiver, sender_original) = UdpStream::with_bound(udp_socket, address); loop { tokio::select! { @@ -87,17 +85,38 @@ impl CoreDns { continue; } }; - self.process_message(msg_received, &sender_original); + self.process_message(msg_received, &sender_original, Protocol::Udp); }, + res = tcp_listener.accept() => { + match res { + Ok((sock,addr)) => { + self.process_tcp_stream(sock, addr).await + } + Err(e) => { + error!("Failed to accept new tcp connection: {e}"); + break; + } + } + } } } Ok(()) } + async fn process_tcp_stream(&self, stream: tokio::net::TcpStream, peer: SocketAddr) { + let (mut hickory_stream, sender_original) = + TcpStream::from_stream(AsyncIoTokioAsStd(stream), peer); + + while let Some(message) = hickory_stream.next().await { + self.process_message(message, &sender_original, Protocol::Tcp) + } + } + fn process_message( &self, msg_received: Result<SerialMessage, Error>, sender_original: &BufDnsStreamHandle, + proto: Protocol, ) { let msg = match msg_received { Ok(msg) => msg, @@ -198,20 +217,34 @@ impl CoreDns { tokio::spawn(async move { // forward dns request to hosts's /etc/resolv.conf for nameserver in &upstream_resolvers { - let connection = - UdpClientStream::<UdpSocket>::new(SocketAddr::new(nameserver.into(), 53)); + let addr = SocketAddr::new(nameserver.into(), 53); + let (client, handle) = match proto { + Protocol::Udp => { + let stream = UdpClientStream::<UdpSocket>::new(addr); + let (cl, bg) = AsyncClient::connect(stream).await?; + let handle = tokio::spawn(bg); + (cl, handle) + } + Protocol::Tcp => { + let (stream, sender) = TcpClientStream::< + AsyncIoTokioAsStd<tokio::net::TcpStream>, + >::new(addr); + let (cl, bg) = AsyncClient::new(stream, sender, None).await?; + let handle = tokio::spawn(bg); + (cl, handle) + } + }; - if let Ok((cl, req_sender)) = AsyncClient::connect(connection).await { - tokio::spawn(req_sender); - if let Some(resp) = forward_dns_req(cl, req.clone()).await { - if reply(&mut sender, src_address, &resp).is_some() { - // request resolved from following resolver so - // break and don't try other resolvers - break; - } + if let Some(resp) = forward_dns_req(client, req.clone()).await { + if reply(&mut sender, src_address, &resp).is_some() { + // request resolved from following resolver so + // break and don't try other resolvers + break; } } + drop(handle); } + Ok::<(), std::io::Error>(()) }); } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..6c81f53 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,118 @@ +use std::fmt; + +pub type AardvarkResult<T> = Result<T, AardvarkError>; + +#[derive(Debug)] +pub enum AardvarkError { + Message(String), + IOError(std::io::Error), + Chain(String, Box<Self>), + List(AardvarkErrorList), + ResolvConfParseError(resolv_conf::ParseError), +} + +impl AardvarkError { + pub fn msg<S>(msg: S) -> Self + where + S: Into<String>, + { + Self::Message(msg.into()) + } + + pub fn wrap<S>(msg: S, chained: Self) -> Self + where + S: Into<String>, + { + Self::Chain(msg.into(), Box::new(chained)) + } +} + +pub trait AardvarkWrap<T, E> { + /// Wrap the error value with additional context. + fn wrap<C>(self, context: C) -> AardvarkResult<T> + where + C: Into<String>, + E: Into<AardvarkError>; +} + +impl<T, E> AardvarkWrap<T, E> for Result<T, E> +where + E: Into<AardvarkError>, +{ + fn wrap<C>(self, msg: C) -> AardvarkResult<T> + where + C: Into<String>, + E: Into<AardvarkError>, + { + // Not using map_err to save 2 useless frames off the captured backtrace + // in ext_context. + match self { + Ok(ok) => Ok(ok), + Err(error) => Err(AardvarkError::wrap(msg, error.into())), + } + } +} + +impl fmt::Display for AardvarkError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Message(s) => write!(f, "{s}"), + Self::Chain(s, e) => write!(f, "{s}: {e}"), + Self::IOError(e) => write!(f, "IO error: {e}"), + Self::ResolvConfParseError(e) => write!(f, "parse resolv.conf: {e}"), + Self::List(list) => { + // some extra code to only add \n when it contains multiple errors + let mut iter = list.0.iter(); + if let Some(first) = iter.next() { + write!(f, "{first}")?; + } + for err in iter { + write!(f, "\n{err}")?; + } + Ok(()) + } + } + } +} + +impl From<std::io::Error> for AardvarkError { + fn from(err: std::io::Error) -> Self { + Self::IOError(err) + } +} + +impl From<nix::Error> for AardvarkError { + fn from(err: nix::Error) -> Self { + Self::IOError(err.into()) + } +} + +impl From<resolv_conf::ParseError> for AardvarkError { + fn from(err: resolv_conf::ParseError) -> Self { + Self::ResolvConfParseError(err) + } +} + +#[derive(Debug)] +pub struct AardvarkErrorList(Vec<AardvarkError>); + +impl AardvarkErrorList { + pub fn new() -> Self { + Self(vec![]) + } + + pub fn push(&mut self, err: AardvarkError) { + self.0.push(err) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +// we do not need it but clippy wants it +impl Default for AardvarkErrorList { + fn default() -> Self { + Self::new() + } +} @@ -2,4 +2,5 @@ pub mod backend; pub mod commands; pub mod config; pub mod dns; +pub mod error; pub mod server; diff --git a/src/main.rs b/src/main.rs index f1a20a9..3bf15a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ struct Opts { config: Option<String>, /// Host port for aardvark servers, defaults to 5533 #[clap(short, long)] - port: Option<u32>, + port: Option<u16>, /// Filters search domain for backward compatiblity with dnsname/dnsmasq #[clap(short, long)] filter_search_domain: Option<String>, @@ -65,7 +65,7 @@ fn main() { let opts = Opts::parse(); let dir = opts.config.unwrap_or_else(|| String::from("/dev/stdin")); - let port = opts.port.unwrap_or(5533_u32); + let port = opts.port.unwrap_or(5533_u16); let filter_search_domain = opts .filter_search_domain .unwrap_or_else(|| String::from(".dns.podman")); diff --git a/src/server/serve.rs b/src/server/serve.rs index 7d5e43f..0b2aaef 100644 --- a/src/server/serve.rs +++ b/src/server/serve.rs @@ -2,7 +2,10 @@ use crate::backend::DNSBackend; use crate::config::constants::AARDVARK_PID_FILE; use crate::config::parse_configs; use crate::dns::coredns::CoreDns; -use anyhow::Context; +use crate::error::AardvarkError; +use crate::error::AardvarkErrorList; +use crate::error::AardvarkResult; +use crate::error::AardvarkWrap; use arc_swap::ArcSwap; use log::{debug, error, info}; use nix::unistd; @@ -18,10 +21,12 @@ use std::io::Error; use std::net::IpAddr; use std::net::Ipv4Addr; use std::net::Ipv6Addr; +use std::net::SocketAddr; use std::os::fd::AsRawFd; use std::os::fd::OwnedFd; use std::sync::Arc; use std::sync::OnceLock; +use tokio::net::{TcpListener, UdpSocket}; use tokio::signal::unix::{signal, SignalKind}; use tokio::task::JoinHandle; @@ -31,7 +36,7 @@ use std::path::Path; use std::process; type ThreadHandleMap<Ip> = - HashMap<(String, Ip), (flume::Sender<()>, JoinHandle<Result<(), anyhow::Error>>)>; + HashMap<(String, Ip), (flume::Sender<()>, JoinHandle<AardvarkResult<()>>)>; pub fn create_pid(config_path: &str) -> Result<(), std::io::Error> { // before serving write its pid to _config_path so other process can notify @@ -61,10 +66,10 @@ pub fn create_pid(config_path: &str) -> Result<(), std::io::Error> { #[tokio::main] pub async fn serve( config_path: &str, - port: u32, + port: u16, filter_search_domain: &str, ready: OwnedFd, -) -> anyhow::Result<()> { +) -> AardvarkResult<()> { let mut signals = signal(SignalKind::hangup())?; let no_proxy: bool = env::var("AARDVARK_NO_PROXY").is_ok(); @@ -102,7 +107,7 @@ pub async fn serve( .await { // do not exit here, we just keep running even if something failed - error!("{e:#}"); + error!("{e}"); }; } } @@ -112,13 +117,14 @@ pub async fn serve( /// Stop threads corresponding to listen IPs no longer in the configuration and start threads /// corresponding to listen IPs that were added. async fn stop_and_start_threads<'a, Ip>( - port: u32, + port: u16, backend: &'static ArcSwap<DNSBackend>, listen_ips: HashMap<String, Vec<Ip>>, thread_handles: &mut ThreadHandleMap<Ip>, no_proxy: bool, nameservers: &'a [ScopedIp], -) where +) -> AardvarkResult<()> +where Ip: Eq + Hash + Copy + Into<IpAddr> + Send + 'static, { let mut expected_threads = HashSet::new(); @@ -144,16 +150,42 @@ async fn stop_and_start_threads<'a, Ip>( .filter(|k| !thread_handles.contains_key(*k)) .cloned() .collect(); + + let mut errors = AardvarkErrorList::new(); + for (network_name, ip) in to_start { let (shutdown_tx, shutdown_rx) = flume::bounded(0); let network_name_ = network_name.clone(); let ns = nameservers.to_owned(); + let addr = SocketAddr::new(ip.into(), port); + let udp_sock = match UdpSocket::bind(addr).await { + Ok(s) => s, + Err(err) => { + errors.push(AardvarkError::wrap( + format!("failed to bind udp listener on {addr}"), + err.into(), + )); + continue; + } + }; + + let tcp_sock = match TcpListener::bind(addr).await { + Ok(s) => s, + Err(err) => { + errors.push(AardvarkError::wrap( + format!("failed to bind tcp listener on {addr}"), + err.into(), + )); + continue; + } + }; + let handle = tokio::spawn(async move { start_dns_server( network_name_, - ip.into(), + udp_sock, + tcp_sock, backend, - port, shutdown_rx, no_proxy, ns, @@ -163,6 +195,12 @@ async fn stop_and_start_threads<'a, Ip>( thread_handles.insert((network_name, ip), (shutdown_tx, handle)); } + + if errors.is_empty() { + return Ok(()); + } + + Err(AardvarkError::List(errors)) } /// # Stop DNS server threads @@ -190,8 +228,7 @@ async fn stop_threads<Ip>( // result returned by the future, i.e. that actual // result from start_dns_server() if let Err(e) = res { - // special anyhow error format to include cause but do not print backtrace - error!("Error from dns server: {:#}", e) + error!("Error from dns server: {}", e) } } // error from tokio itself @@ -202,27 +239,30 @@ async fn stop_threads<Ip>( async fn start_dns_server( name: String, - addr: IpAddr, + udp_socket: UdpSocket, + tcp_socket: TcpListener, backend: &'static ArcSwap<DNSBackend>, - port: u32, rx: flume::Receiver<()>, no_proxy: bool, nameservers: Vec<ScopedIp>, -) -> Result<(), anyhow::Error> { - let mut server = CoreDns::new(addr, port, name, backend, rx, no_proxy, nameservers); - server.run().await.context("run dns server") +) -> AardvarkResult<()> { + let server = CoreDns::new(name, backend, rx, no_proxy, nameservers); + server + .run(udp_socket, tcp_socket) + .await + .wrap("run dns server") } async fn read_config_and_spawn( config_path: &str, - port: u32, + port: u16, filter_search_domain: &str, handles_v4: &mut ThreadHandleMap<Ipv4Addr>, handles_v6: &mut ThreadHandleMap<Ipv6Addr>, no_proxy: bool, -) -> anyhow::Result<()> { +) -> AardvarkResult<()> { let (conf, listen_ip_v4, listen_ip_v6) = - parse_configs(config_path, filter_search_domain).context("unable to parse config")?; + parse_configs(config_path, filter_search_domain).wrap("unable to parse config")?; // We store the `DNSBackend` in an `ArcSwap` so we can replace it when the configuration is // reloaded. @@ -256,10 +296,21 @@ async fn read_config_and_spawn( process::exit(0); } + let mut errors = AardvarkErrorList::new(); + // get host nameservers - let nameservers = get_upstream_resolvers().context("failed to get upstream nameservers")?; + let nameservers = match get_upstream_resolvers() { + Ok(ns) => ns, + Err(err) => { + errors.push(AardvarkError::wrap( + "failed to get upstream nameservers, dns forwarding will not work", + err, + )); + Vec::new() + } + }; - stop_and_start_threads( + if let Err(err) = stop_and_start_threads( port, backend, listen_ip_v4, @@ -267,9 +318,12 @@ async fn read_config_and_spawn( no_proxy, &nameservers, ) - .await; + .await + { + errors.push(err) + }; - stop_and_start_threads( + if let Err(err) = stop_and_start_threads( port, backend, listen_ip_v6, @@ -277,9 +331,16 @@ async fn read_config_and_spawn( no_proxy, &nameservers, ) - .await; + .await + { + errors.push(err) + }; - Ok(()) + if errors.is_empty() { + return Ok(()); + } + + Err(AardvarkError::List(errors)) } // creates new session and put /dev/null on the stdio streams @@ -302,10 +363,10 @@ fn daemonize() -> Result<(), Error> { } // read /etc/resolv.conf and return all nameservers -fn get_upstream_resolvers() -> Result<Vec<ScopedIp>, anyhow::Error> { - let mut f = File::open("/etc/resolv.conf").context("open resolv.conf")?; +fn get_upstream_resolvers() -> AardvarkResult<Vec<ScopedIp>> { + let mut f = File::open("/etc/resolv.conf").wrap("open resolv.conf")?; let mut buf = Vec::with_capacity(4096); - f.read_to_end(&mut buf).context("read resolv.conf")?; - let conf = resolv_conf::Config::parse(buf).context("parse resolv.conf")?; + f.read_to_end(&mut buf).wrap("read resolv.conf")?; + let conf = resolv_conf::Config::parse(buf)?; Ok(conf.nameservers) } diff --git a/src/test/test.rs b/src/test/test.rs index b1de20d..8cb7eaa 100644 --- a/src/test/test.rs +++ b/src/test/test.rs @@ -10,18 +10,16 @@ mod tests { use aardvark_dns::backend::{DNSBackend, DNSResult}; use aardvark_dns::config; + use aardvark_dns::error::AardvarkResult; use std::str::FromStr; fn parse_configs( dir: &str, - ) -> Result< - ( - DNSBackend, - HashMap<String, Vec<Ipv4Addr>>, - HashMap<String, Vec<Ipv6Addr>>, - ), - std::io::Error, - > { + ) -> AardvarkResult<( + DNSBackend, + HashMap<String, Vec<Ipv4Addr>>, + HashMap<String, Vec<Ipv6Addr>>, + )> { config::parse_configs(dir, "") } diff --git a/test/100-basic-name-resolution.bats b/test/100-basic-name-resolution.bats index e178717..b396eb1 100644 --- a/test/100-basic-name-resolution.bats +++ b/test/100-basic-name-resolution.bats @@ -71,12 +71,28 @@ load helpers # contain unexpected warning. assert "$output" !~ "WARNING: recursion requested but not available" + # check TCP support + run_in_container_netns "$a1_pid" "dig" "+tcp" "+short" "aone" "@$gw" + assert "$ip_a1" + + run_in_container_netns "$a1_pid" "dig" "+short" "google.com" "@$gw" # validate that we get an ipv4 assert "$output" =~ "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" # Set recursion bit is already set if requested so output must not # contain unexpected warning. assert "$output" !~ "WARNING: recursion requested but not available" + + # check TCP support for forwarding + # note there is no guarantee that the forwarding is happening via TCP though + # TODO add custom dns record that is to big for udp so we can be sure... + run_in_container_netns "$a1_pid" "dig" "+tcp" "google.com" "@$gw" + # validate that we get an ipv4 + assert "$output" =~ "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" + assert "$output" =~ "\(TCP\)" "server used TCP" + # Set recursion bit is already set if requested so output must not + # contain unexpected warning. + assert "$output" !~ "WARNING: recursion requested but not available" } @test "basic container - ndots incomplete bad entry must NXDOMAIN instead of forwarding and timing out" { diff --git a/test/600-errors.bats b/test/600-errors.bats new file mode 100644 index 0000000..96b1dbf --- /dev/null +++ b/test/600-errors.bats @@ -0,0 +1,47 @@ +#!/usr/bin/env bats -*- bats -*- +# +# basic netavark tests +# + +load helpers + + +NCPID= + +function teardown() { + kill -9 $NCPID + basic_teardown +} + +# check bind error on startup +@test "aardvark-dns should fail when udp port is already bound" { + # bind the port to force a failure for aardvark-dns + # we cannot use run_is_host_netns to run in the background + nsenter -m -n -t $HOST_NS_PID nc -u -l 0.0.0.0 53 </dev/null 3> /dev/null & + NCPID=$! + + # ensure nc has time to bind the port + sleep 1 + + subnet_a=$(random_subnet 5) + create_config network_name="podman1" container_id=$(random_string 64) container_name="aone" subnet="$subnet_a" + gw=$(echo "$config" | jq -r .network_info.podman1.subnets[0].gateway) + expected_rc=1 create_container "$config" + assert "$output" =~ "failed to bind udp listener on $gw:53" "bind error message" +} + +@test "aardvark-dns should fail when tcp port is already bound" { + # bind the port to force a failure for aardvark-dns + # we cannot use run_is_host_netns to run in the background + nsenter -m -n -t $HOST_NS_PID nc -l 0.0.0.0 53 </dev/null 3> /dev/null & + NCPID=$! + + # ensure nc has time to bind the port + sleep 1 + + subnet_a=$(random_subnet 5) + create_config network_name="podman1" container_id=$(random_string 64) container_name="aone" subnet="$subnet_a" + gw=$(echo "$config" | jq -r .network_info.podman1.subnets[0].gateway) + expected_rc=1 create_container "$config" + assert "$output" =~ "failed to bind tcp listener on $gw:53" "bind error message" +} diff --git a/test/helpers.bash b/test/helpers.bash index 9537af8..bd8faaf 100644 --- a/test/helpers.bash +++ b/test/helpers.bash @@ -501,8 +501,8 @@ EOF function create_container() { CONTAINER_NS_PID=$(create_netns) CONTAINER_NS_PIDS+=("$CONTAINER_NS_PID") - create_container_backend "$CONTAINER_NS_PID" "$1" CONTAINER_CONFIGS+=("$1") + create_container_backend "$CONTAINER_NS_PID" "$1" } # arg1 is pid @@ -559,17 +559,6 @@ function setup_slirp4netns() { } function basic_teardown() { - rm -fr "$AARDVARK_TMPDIR" -} - -################ -# netavark_teardown# tears down a network -################ -function netavark_teardown() { - run_netavark teardown $1 <<<"$2" -} - -function teardown() { # Now call netavark with all the configs and then kill the netns associated with it for i in "${!CONTAINER_CONFIGS[@]}"; do netavark_teardown $(get_container_netns_path "${CONTAINER_NS_PIDS[$i]}") "${CONTAINER_CONFIGS[$i]}" @@ -587,6 +576,17 @@ function teardown() { kill -9 "$HOST_NS_PID" fi + rm -fr "$AARDVARK_TMPDIR" +} + +################ +# netavark_teardown# tears down a network +################ +function netavark_teardown() { + run_netavark teardown $1 <<<"$2" +} + +function teardown() { basic_teardown } |