Skip to content

Commit

Permalink
Merge pull request #518 from Luap99/resolv-conf
Browse files Browse the repository at this point in the history
serve: parse resolv.conf ourselves
  • Loading branch information
openshift-merge-bot[bot] authored Sep 25, 2024
2 parents 56d105f + 22293ef commit c283c98
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 43 deletions.
16 changes: 0 additions & 16 deletions Cargo.lock

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

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ hickory-proto = { version = "0.24.1", features = ["tokio-runtime"] }
hickory-client = "0.24.1"
futures-util = { version = "0.3.30", default-features = false }
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread", "net", "signal"] }
resolv-conf = "0.7.0"
nix = { version = "0.29.0", features = ["fs", "signal"] }
libc = "0.2.159"
arc-swap = "1.7.1"
Expand Down
22 changes: 10 additions & 12 deletions src/dns/coredns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ use hickory_proto::{
DnsStreamHandle,
};
use log::{debug, error, trace, warn};
use resolv_conf;
use resolv_conf::ScopedIp;
use std::io::Error;
use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
Expand All @@ -40,10 +38,10 @@ pub struct CoreDns {

#[derive(Clone)]
struct CoreDnsData {
network_name: String, // raw network name
backend: &'static ArcSwap<DNSBackend>, // server's data store
no_proxy: bool, // do not forward to external resolvers
nameservers: Arc<Mutex<Vec<ScopedIp>>>, // host nameservers from resolv.conf
network_name: String, // raw network name
backend: &'static ArcSwap<DNSBackend>, // server's data store
no_proxy: bool, // do not forward to external resolvers
nameservers: Arc<Mutex<Vec<IpAddr>>>, // host nameservers from resolv.conf
}

enum Protocol {
Expand All @@ -59,7 +57,7 @@ impl CoreDns {
backend: &'static ArcSwap<DNSBackend>,
rx: flume::Receiver<()>,
no_proxy: bool,
nameservers: Arc<Mutex<Vec<ScopedIp>>>,
nameservers: Arc<Mutex<Vec<IpAddr>>>,
) -> Self {
CoreDns {
rx,
Expand Down Expand Up @@ -219,18 +217,18 @@ impl CoreDns {
"Forwarding dns request for {} type: {}",
&request_name_string, record_type
);
let mut nameservers: Vec<ScopedIp> = Vec::new();
let mut nameservers: Vec<IpAddr> = Vec::new();
// Add resolvers configured for container
if let Some(Some(dns_servers)) = backend.ctr_dns_server.get(&src_address.ip()) {
for dns_server in dns_servers.iter() {
nameservers.push(ScopedIp::from(*dns_server));
nameservers.push(*dns_server);
}
// Add network scoped resolvers only if container specific resolvers were not configured
} else if let Some(network_dns_servers) =
backend.get_network_scoped_resolvers(&src_address.ip())
{
for dns_server in network_dns_servers.iter() {
nameservers.push(ScopedIp::from(*dns_server));
nameservers.push(*dns_server);
}
}
// Use host resolvers if no custom resolvers are set for the container.
Expand All @@ -257,15 +255,15 @@ impl CoreDns {
}

async fn forward_to_servers(
nameservers: Vec<ScopedIp>,
nameservers: Vec<IpAddr>,
mut sender: BufDnsStreamHandle,
src_address: SocketAddr,
req: Message,
proto: Protocol,
) {
// forward dns request to hosts's /etc/resolv.conf
for nameserver in &nameservers {
let addr = SocketAddr::new(nameserver.into(), 53);
let addr = SocketAddr::new(*nameserver, 53);
let (client, handle) = match proto {
Protocol::Udp => {
let stream = UdpClientStream::<UdpSocket>::new(addr);
Expand Down
10 changes: 5 additions & 5 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub enum AardvarkError {
IOError(std::io::Error),
Chain(String, Box<Self>),
List(AardvarkErrorList),
ResolvConfParseError(resolv_conf::ParseError),
AddrParseError(std::net::AddrParseError),
}

impl AardvarkError {
Expand Down Expand Up @@ -59,7 +59,7 @@ impl fmt::Display for AardvarkError {
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::AddrParseError(e) => write!(f, "parse address: {e}"),
Self::List(list) => {
// some extra code to only add \n when it contains multiple errors
let mut iter = list.0.iter();
Expand Down Expand Up @@ -87,9 +87,9 @@ impl From<nix::Error> for AardvarkError {
}
}

impl From<resolv_conf::ParseError> for AardvarkError {
fn from(err: resolv_conf::ParseError) -> Self {
Self::ResolvConfParseError(err)
impl From<std::net::AddrParseError> for AardvarkError {
fn from(err: std::net::AddrParseError) -> Self {
Self::AddrParseError(err)
}
}

Expand Down
115 changes: 106 additions & 9 deletions src/server/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use arc_swap::ArcSwap;
use log::{debug, error, info};
use nix::unistd;
use nix::unistd::dup2;
use resolv_conf::ScopedIp;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
Expand Down Expand Up @@ -126,7 +125,7 @@ async fn stop_and_start_threads<Ip>(
listen_ips: HashMap<String, Vec<Ip>>,
thread_handles: &mut ThreadHandleMap<Ip>,
no_proxy: bool,
nameservers: Arc<Mutex<Vec<ScopedIp>>>,
nameservers: Arc<Mutex<Vec<IpAddr>>>,
) -> AardvarkResult<()>
where
Ip: Eq + Hash + Copy + Into<IpAddr> + Send + 'static,
Expand Down Expand Up @@ -248,7 +247,7 @@ async fn start_dns_server(
backend: &'static ArcSwap<DNSBackend>,
rx: flume::Receiver<()>,
no_proxy: bool,
nameservers: Arc<Mutex<Vec<ScopedIp>>>,
nameservers: Arc<Mutex<Vec<IpAddr>>>,
) -> AardvarkResult<()> {
let server = CoreDns::new(name, backend, rx, no_proxy, nameservers);
server
Expand All @@ -263,7 +262,7 @@ async fn read_config_and_spawn(
filter_search_domain: &str,
handles_v4: &mut ThreadHandleMap<Ipv4Addr>,
handles_v6: &mut ThreadHandleMap<Ipv6Addr>,
nameservers: Arc<Mutex<Vec<ScopedIp>>>,
nameservers: Arc<Mutex<Vec<IpAddr>>>,
no_proxy: bool,
) -> AardvarkResult<()> {
let (conf, listen_ip_v4, listen_ip_v6) =
Expand Down Expand Up @@ -314,6 +313,7 @@ async fn read_config_and_spawn(
Vec::new()
}
};
debug!("Using the following upstream servers: {upstream_resolvers:?}");

{
// use new scope to only lock for a short time
Expand Down Expand Up @@ -373,10 +373,107 @@ fn daemonize() -> Result<(), Error> {
}

// read /etc/resolv.conf and return all nameservers
fn get_upstream_resolvers() -> AardvarkResult<Vec<ScopedIp>> {
fn get_upstream_resolvers() -> AardvarkResult<Vec<IpAddr>> {
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).wrap("read resolv.conf")?;
let conf = resolv_conf::Config::parse(buf)?;
Ok(conf.nameservers)
let mut buf = String::with_capacity(4096);
f.read_to_string(&mut buf).wrap("read resolv.conf")?;

parse_resolv_conf(&buf)
}

fn parse_resolv_conf(content: &str) -> AardvarkResult<Vec<IpAddr>> {
let mut nameservers: Vec<IpAddr> = Vec::new();
for line in content.split('\n') {
// split of comments
let line = match line.split_once(|s| s == '#' || s == ';') {
Some((f, _)) => f,
None => line,
};
let mut line_parts = line.split_whitespace();
match line_parts.next() {
Some(first) => {
if first == "nameserver" {
if let Some(ip) = line_parts.next() {
// split of zone, we do not support the link local zone currently with ipv6 addresses
let ip = match ip.split_once("%s") {
Some((f, _)) => f,
None => ip,
};
nameservers.push(ip.parse().wrap(ip)?);
}
}
}
None => continue,
}
}
Ok(nameservers)
}

#[cfg(test)]
mod tests {
use super::*;

const IP_1_1_1_1: IpAddr = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
const IP_1_1_1_2: IpAddr = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 2));
const IP_1_1_1_3: IpAddr = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 3));

#[test]
fn test_parse_resolv_conf() {
let res = parse_resolv_conf("nameserver 1.1.1.1").expect("failed to parse");
assert_eq!(res, vec![IP_1_1_1_1]);
}

#[test]
fn test_parse_resolv_conf_multiple() {
let res = parse_resolv_conf(
"nameserver 1.1.1.1
nameserver 1.1.1.2
nameserver 1.1.1.3",
)
.expect("failed to parse");
assert_eq!(res, vec![IP_1_1_1_1, IP_1_1_1_2, IP_1_1_1_3]);
}

#[test]
fn test_parse_resolv_conf_search_and_options() {
let res = parse_resolv_conf(
"nameserver 1.1.1.1
nameserver 1.1.1.2
nameserver 1.1.1.3
search test.podman
options rotate",
)
.expect("failed to parse");
assert_eq!(res, vec![IP_1_1_1_1, IP_1_1_1_2, IP_1_1_1_3]);
}
#[test]
fn test_parse_resolv_conf_with_comment() {
let res = parse_resolv_conf(
"# mytest
nameserver 1.1.1.1 # space
nameserver 1.1.1.2#nospace
#leading spaces
nameserver 1.1.1.3",
)
.expect("failed to parse");
assert_eq!(res, vec![IP_1_1_1_1, IP_1_1_1_2, IP_1_1_1_3]);
}

#[test]
fn test_parse_resolv_conf_with_invalid_content() {
let res = parse_resolv_conf(
"hey I am not known
nameserver 1.1.1.1
nameserver 1.1.1.2 somestuff here
abc
nameserver 1.1.1.3",
)
.expect("failed to parse");
assert_eq!(res, vec![IP_1_1_1_1, IP_1_1_1_2, IP_1_1_1_3]);
}

#[test]
fn test_parse_resolv_conf_with_invalid_ip() {
parse_resolv_conf("nameserver abc").expect_err("invalid ip must error");
}
}

0 comments on commit c283c98

Please sign in to comment.