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 10, 2022
1 parent 9331c87 commit 6b06fe8
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 13 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
65 changes: 52 additions & 13 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ fn parse_config(path: &std::path::Path) -> Result<(Vec<IpAddr>, Vec<CtrEntry>),
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 +206,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 +227,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 @@ -281,20 +308,32 @@ fn parse_config(path: &std::path::Path) -> Result<(Vec<IpAddr>, Vec<CtrEntry>),
));
}

let dns_servers: Option<Vec<IpAddr>> = if parts.len() == 5 && !parts[4].is_empty() {
let dns_server = match parts[4].split(',').map(|i| i.parse()).collect() {
Ok(i) => i,
Err(e) => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("error parsing DNS server address {}: {}", parts[4], e),
))
let dns_servers: Option<Vec<IpAddr>> =
if parts.len() == 5 && !parts[4].is_empty() || !network_dns_servers.is_empty() {
let mut dns_server: Vec<IpAddr> = Vec::new();
// If network dns servers were specified then all the
// containers in this specified network must inherit
// network dns servers.
if !network_dns_servers.is_empty() {
dns_server.extend(&network_dns_servers);
}
if parts.len() == 5 && !parts[4].is_empty() {
let ctr_dns_server: Vec<IpAddr> =
match parts[4].split(',').map(|i| i.parse()).collect() {
Ok(i) => i,
Err(e) => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("error parsing DNS server address {}: {}", parts[4], e),
))
}
};
dns_server.extend(&ctr_dns_server);
}
Some(dns_server)
} else {
None
};
Some(dns_server)
} else {
None
};

ctrs.push(CtrEntry {
id: parts[0].to_string().to_lowercase(),
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
52 changes: 52 additions & 0 deletions src/test/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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 +88,52 @@ 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, _, _)) => {
// Should contain custom DNS server 8.8.8.8
let mut dns_server = backend
.ctr_dns_server
.get(&IpAddr::V4(Ipv4Addr::new(10, 88, 0, 2)));
let mut expected_dns_server = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
assert_eq!(dns_server.unwrap().clone().unwrap()[0], expected_dns_server);
expected_dns_server = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2));
assert_eq!(dns_server.unwrap().clone().unwrap()[1], expected_dns_server);
expected_dns_server = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8));
assert_eq!(dns_server.unwrap().clone().unwrap()[2], expected_dns_server);

// Should contain custom DNS servers 3.3.3.3 and 1.1.1.1
dns_server = backend
.ctr_dns_server
.get(&IpAddr::V4(Ipv4Addr::new(10, 88, 0, 5)));
expected_dns_server = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
assert_eq!(dns_server.unwrap().clone().unwrap()[0], expected_dns_server);
expected_dns_server = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2));
assert_eq!(dns_server.unwrap().clone().unwrap()[1], expected_dns_server);
expected_dns_server = IpAddr::V4(Ipv4Addr::new(3, 3, 3, 3));
assert_eq!(dns_server.unwrap().clone().unwrap()[2], expected_dns_server);
expected_dns_server = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
assert_eq!(dns_server.unwrap().clone().unwrap()[3], expected_dns_server);
expected_dns_server = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
assert_eq!(dns_server.unwrap().clone().unwrap()[4], expected_dns_server);

// Shoudld not contain any DNS server other than network's dns server
dns_server = backend
.ctr_dns_server
.get(&IpAddr::V4(Ipv4Addr::new(10, 88, 0, 3)));
expected_dns_server = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
assert_eq!(dns_server.unwrap().clone().unwrap()[0], expected_dns_server);
expected_dns_server = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2));
assert_eq!(dns_server.unwrap().clone().unwrap()[1], expected_dns_server);
}
Err(e) => panic!("{}", e),
}
}

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

0 comments on commit 6b06fe8

Please sign in to comment.