Skip to content

Commit

Permalink
config,coredns: add support for network scoped dns servers
Browse files Browse the repository at this point in the history
Aardvark-dns users must be able to specify `dns_servers` for containers
at network level as well, so all the containers must inherit custom
dns servers from their network if specified.

What happens if both container's dns server and network's dns server are
specified ?

- In such case priority order is `network dns servers` and then
  container's `custom dns servers`.

By default if nothing is specified resolution will happen from host's
resolver

Signed-off-by: Aditya R <[email protected]>
  • Loading branch information
flouthoc committed Nov 22, 2022
1 parent 9331c87 commit 64fabeb
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 9 deletions.
15 changes: 15 additions & 0 deletions config.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ Aardvark-dns will read configuration files from a given directory.

Inside this directory there should be at least one config file. The name of the file equals the network name.

### First line
The first line in the config must contain a comma separated list of listening ips for this network, usually the bridge ips.
At least one ip must be given.
**Note**: Optional second coloumn allows users to specifify DNS servers at network level (seperated by comman), all the containers
will inherit following nameservers instead of using host's resolver.

```
[comma seperated ip4,ipv6 list][(optional)[space][comma seperated DNS servers]]
```

### Container entries
All following lines must contain the dns entries in this format:
```
[containerID][space][comma sparated ipv4 list][space][comma separated ipv6 list][space][comma separated dns names][(optional)[space][comma seperated DNS servers]]
Expand All @@ -22,5 +30,12 @@ Aardvark-dns will reload all config files when receiving a SIGHUB signal.
f35256b5e2f72ec8cb7d974d4f8841686fc8921fdfbc867285b50164e313f715 10.0.0.2 fdfd::2 testmulti1 8.8.8.8,1.1.1.1
e5df0cdbe0136a30cc3e848d495d2cc6dada25b7dedc776b4584ce2cbba6f06f 10.0.0.3 fdfd::3 testmulti2
```
## Example with network scoped DNS servers

```
10.0.0.1,fdfd::1 8.8.8.8,1.1.1.1
f35256b5e2f72ec8cb7d974d4f8841686fc8921fdfbc867285b50164e313f715 10.0.0.2 fdfd::2 testmulti1 8.8.8.8,1.1.1.1
e5df0cdbe0136a30cc3e848d495d2cc6dada25b7dedc776b4584ce2cbba6f06f 10.0.0.3 fdfd::3 testmulti2
```

Also see [./src/test/config/](./src/test/config/) for more config examples
25 changes: 25 additions & 0 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub struct DNSBackend {
// Map of IP address to DNS server IPs to service queries not handled
// directly.
pub ctr_dns_server: HashMap<IpAddr, Option<Vec<IpAddr>>>,
// Map of network name and DNS server IPs.
pub network_dns_server: HashMap<String, Vec<IpAddr>>,
}

pub enum DNSResult {
Expand All @@ -42,12 +44,14 @@ impl DNSBackend {
networks: HashMap<String, HashMap<String, Vec<IpAddr>>>,
reverse: HashMap<String, HashMap<IpAddr, Vec<String>>>,
ctr_dns_server: HashMap<IpAddr, Option<Vec<IpAddr>>>,
network_dns_server: HashMap<String, Vec<IpAddr>>,
) -> DNSBackend {
DNSBackend {
ip_mappings: containers,
name_mappings: networks,
reverse_mappings: reverse,
ctr_dns_server,
network_dns_server,
}
}

Expand Down Expand Up @@ -95,6 +99,27 @@ impl DNSBackend {
DNSResult::Success(results)
}

// Returns list of network resolvers for a particular container
pub fn get_network_scoped_resolvers(&self, requester: &IpAddr) -> Option<Vec<IpAddr>> {
let mut results: Vec<IpAddr> = Vec::new();

let nets = match self.ip_mappings.get(requester) {
Some(n) => n,
None => return None,
};

for net in nets {
match self.network_dns_server.get(net) {
Some(resolvers) => results.extend_from_slice(resolvers),
None => {
continue;
}
};
}

Some(results)
}

/// Return a single name resolved via mapping if it exists.
pub fn reverse_lookup(&self, requester: &IpAddr, lookup_ip: &IpAddr) -> Option<&Vec<String>> {
let nets = match self.ip_mappings.get(requester) {
Expand Down
70 changes: 63 additions & 7 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub fn parse_configs(
let mut listen_ips_4: HashMap<String, Vec<Ipv4Addr>> = HashMap::new();
let mut listen_ips_6: HashMap<String, Vec<Ipv6Addr>> = HashMap::new();
let mut ctr_dns_server: HashMap<IpAddr, Option<Vec<IpAddr>>> = HashMap::new();
let mut network_dns_server: HashMap<String, Vec<IpAddr>> = HashMap::new();

// Enumerate all files in the directory, read them in one by one.
// Steadily build a map of what container has what IPs and what
Expand All @@ -63,7 +64,7 @@ pub fn parse_configs(
continue;
}
}
let (bind_ips, ctr_entry) = parse_config(cfg.path().as_path())?;
let parsed_network_config = parse_config(cfg.path().as_path())?;

let network_name: String = match cfg.path().file_name() {
// This isn't *completely* safe, but I do not foresee many
Expand All @@ -82,7 +83,16 @@ pub fn parse_configs(
)),
};

for ip in bind_ips {
// Network DNS Servers were found while parsing config
// lets populate the backend
if !parsed_network_config.network_dnsservers.is_empty() {
network_dns_server.insert(
network_name.clone(),
parsed_network_config.network_dnsservers,
);
}

for ip in parsed_network_config.network_bind_ip {
match ip {
IpAddr::V4(a) => listen_ips_4
.entry(network_name.clone())
Expand All @@ -95,7 +105,7 @@ pub fn parse_configs(
}
}

for entry in ctr_entry {
for entry in parsed_network_config.container_entry {
// Container network membership
let ctr_networks = network_membership
.entry(entry.id.clone())
Expand Down Expand Up @@ -176,7 +186,13 @@ pub fn parse_configs(
}

Ok((
DNSBackend::new(ctrs, network_names, reverse, ctr_dns_server),
DNSBackend::new(
ctrs,
network_names,
reverse,
ctr_dns_server,
network_dns_server,
),
listen_ips_4,
listen_ips_6,
))
Expand All @@ -191,12 +207,22 @@ struct CtrEntry {
dns_servers: Option<Vec<IpAddr>>,
}

// A simplified type for results retured by
// parse_config after parsing a single network
// config.
struct ParsedNetworkConfig {
network_bind_ip: Vec<IpAddr>,
container_entry: Vec<CtrEntry>,
network_dnsservers: Vec<IpAddr>,
}

// Read and parse a single given configuration file
fn parse_config(path: &std::path::Path) -> Result<(Vec<IpAddr>, Vec<CtrEntry>), std::io::Error> {
fn parse_config(path: &std::path::Path) -> Result<ParsedNetworkConfig, std::io::Error> {
let content = read_to_string(path)?;
let mut is_first = true;

let mut bind_addrs: Vec<IpAddr> = Vec::new();
let mut network_dns_servers: Vec<IpAddr> = Vec::new();
let mut ctrs: Vec<CtrEntry> = Vec::new();

// Split on newline, parse each line
Expand All @@ -205,7 +231,15 @@ fn parse_config(path: &std::path::Path) -> Result<(Vec<IpAddr>, Vec<CtrEntry>),
continue;
}
if is_first {
for ip in line.split(',') {
let network_parts = line.split(' ').collect::<Vec<&str>>();
if network_parts.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("invalid network configuration file: {}", path.display()),
));
}
// process bind ip
for ip in network_parts[0].split(',') {
let local_ip = match ip.parse() {
Ok(l) => l,
Err(e) => {
Expand All @@ -218,6 +252,24 @@ fn parse_config(path: &std::path::Path) -> Result<(Vec<IpAddr>, Vec<CtrEntry>),
bind_addrs.push(local_ip);
}

// If network parts contain more than one col then
// we have custom dns server also defined at network level
// lets process that.
if network_parts.len() > 1 {
for ip in network_parts[1].split(',') {
let local_ip = match ip.parse() {
Ok(l) => l,
Err(e) => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("error parsing network dns address {}: {}", ip, e),
))
}
};
network_dns_servers.push(local_ip);
}
}

is_first = false;
continue;
}
Expand Down Expand Up @@ -316,5 +368,9 @@ fn parse_config(path: &std::path::Path) -> Result<(Vec<IpAddr>, Vec<CtrEntry>),
));
}

Ok((bind_addrs, ctrs))
Ok(ParsedNetworkConfig {
network_bind_ip: bind_addrs,
container_entry: ctrs,
network_dnsservers: network_dns_servers,
})
}
15 changes: 13 additions & 2 deletions src/dns/coredns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,26 @@ impl CoreDns {
}
};
let mut resolved_ip_list: Vec<IpAddr> = Vec::new();
let mut nameservers_scoped: Vec<ScopedIp> = Vec::new();
// Add resolvers configured for container
if let Some(Some(dns_servers)) = self.backend.ctr_dns_server.get(&src_address.ip()) {
if !dns_servers.is_empty() {
let mut nameservers_scoped: Vec<ScopedIp> = Vec::new();
for dns_server in dns_servers.iter() {
nameservers_scoped.push(ScopedIp::from(*dns_server));
}
}
}
// Add network scoped resolvers
if let Some(network_dns_servers) = self.backend.get_network_scoped_resolvers(&src_address.ip()) {
for dns_server in network_dns_servers.iter() {
nameservers_scoped.push(ScopedIp::from(*dns_server));
}
}
// Override host resolvers with custom resolvers if any were
// configured for container or network.
if !nameservers_scoped.is_empty() {
dns_resolver = resolv_conf::Config::new();
dns_resolver.nameservers = nameservers_scoped;
}
}

// Create debug and trace info for key parameters.
Expand Down
5 changes: 5 additions & 0 deletions src/test/config/network_scoped_custom_dns/podman
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
10.88.0.1 127.0.0.1,::2
68fb291b0318b54a71f6f3636e58bd0896f084e5ba4fa311ecf36e019c5e6e43 10.88.0.2 condescendingnash 8.8.8.8
68fb291b0318b54a71f6f3636e58bd0896f084e5ba4fa311ecf36e019c5e6e48 10.88.0.5 HelloWorld 3.3.3.3,1.1.1.1,::1
95655fb6832ba134efa66e9c80862a6c9b04f3cc6abf8adfdda8c38112c2c6fa 10.88.0.3 hopefulmontalcini,testdbctr
8bcc5fe0cb09bee5dfb71d61503a87688cfc82aa5f130bcedb19357a17765926 10.88.0.4 trustingzhukovsky,ctr1,ctra
32 changes: 32 additions & 0 deletions src/test/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod tests {

use aardvark_dns::backend::DNSResult;
use aardvark_dns::config;
use std::str::FromStr;
/* -------------------------------------------- */
// --------- Test aardvark-dns config ---------
/* -------------------------------------------- */
Expand All @@ -26,6 +27,12 @@ mod tests {
config::parse_configs("src/test/config/podman_custom_dns_servers").unwrap();
}
#[test]
// Test loading of config file from directory with custom DNS for containers
// and custom DNS servers at network level as well.
fn test_loading_config_file_with_network_scoped_dns_servers() {
config::parse_configs("src/test/config/network_scoped_custom_dns").unwrap();
}
#[test]
// Parse config files from stub data
fn test_parsing_config_files() {
match config::parse_configs("src/test/config/podman") {
Expand Down Expand Up @@ -82,6 +89,31 @@ mod tests {
}
}

#[test]
// Backend must populate ctr_dns_servers via custom
// DNS servers for container from container entry and
// network dns servers as well.
fn test_backend_network_scoped_custom_dns_server() {
match config::parse_configs("src/test/config/network_scoped_custom_dns") {
Ok((backend, _, _)) => {
let expected_dnsservers = vec!["127.0.0.1", "::0.0.0.2"];
let test_cases_source = vec!["10.88.0.2", "10.88.0.3", "10.88.0.4", "10.88.0.5"];
// verify if network scoped resolvers for all the containers is equivalent to
// expectedDNSServers
for container in test_cases_source.iter() {
let output =
backend.get_network_scoped_resolvers(&IpAddr::from_str(container).unwrap());
let mut output_dnsservers = Vec::new();
for server in output.unwrap().iter() {
output_dnsservers.push(format!("{}", server));
}
assert_eq!(expected_dnsservers, output_dnsservers);
}
}
Err(e) => panic!("{}", e),
}
}

/* -------------------------------------------- */
// -------Test aardvark-dns lookup logic ------
/* -------------------------------------------- */
Expand Down

0 comments on commit 64fabeb

Please sign in to comment.