summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoropenshift-merge-bot[bot] <148852131+openshift-merge-bot[bot]@users.noreply.github.com>2024-07-29 14:28:10 +0000
committerGitHub <noreply@github.com>2024-07-29 14:28:10 +0000
commit0db33bb407fb3a56c87158a3f5d4367e46d34309 (patch)
tree7a955d7f48cee025940fe0ed41e53eec445a5af0
parent4ee5d6b23722faf7094926242a35c4417b02f447 (diff)
parent7a5e6e7f82688c2a796d04151191fbf83fb33900 (diff)
Merge pull request #484 from Luap99/tcp
add tcp support
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml3
-rw-r--r--src/commands/run.rs2
-rw-r--r--src/config/mod.rs35
-rw-r--r--src/dns/coredns.rs93
-rw-r--r--src/error.rs118
-rw-r--r--src/lib.rs1
-rw-r--r--src/main.rs4
-rw-r--r--src/server/serve.rs119
-rw-r--r--src/test/test.rs14
-rw-r--r--test/100-basic-name-resolution.bats16
-rw-r--r--test/600-errors.bats47
-rw-r--r--test/helpers.bash24
13 files changed, 371 insertions, 112 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 701611f..9ccfe62 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 17e686b..5cc1a7e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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()
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 280113b..f26bb0f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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
}