use crate::backend::DNSBackend;
use log::warn;
use std::collections::HashMap;
use std::fs::{metadata, read_dir, read_to_string};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::vec::Vec;
pub mod constants;
#[allow(clippy::type_complexity)]
pub fn parse_configs(
dir: &str,
) -> Result<
(
DNSBackend,
HashMap<String, Vec<Ipv4Addr>>,
HashMap<String, Vec<Ipv6Addr>>,
),
std::io::Error,
> {
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),
));
}
let mut network_membership: HashMap<String, Vec<String>> = HashMap::new();
let mut container_ips: HashMap<String, Vec<IpAddr>> = HashMap::new();
let mut reverse: HashMap<String, HashMap<IpAddr, Vec<String>>> = HashMap::new();
let mut network_names: HashMap<String, HashMap<String, Vec<IpAddr>>> = HashMap::new();
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();
let configs = read_dir(dir)?;
for config in configs {
match config {
Ok(cfg) => {
if let Some(path) = cfg.path().file_name() {
if path == constants::AARDVARK_PID_FILE {
continue;
}
}
let parsed_network_config = parse_config(cfg.path().as_path())?;
let network_name: String = match cfg.path().file_name() {
Some(s) => match s.to_str() {
Some(st) => st.to_string(),
None => return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("configuration file {} name has non-UTF8 characters", s.to_string_lossy()),
)),
},
None => return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("configuration file {} does not have a file name, cannot identify network name", cfg.path().to_string_lossy()),
)),
};
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())
.or_insert_with(Vec::new)
.push(a),
IpAddr::V6(b) => listen_ips_6
.entry(network_name.clone())
.or_insert_with(Vec::new)
.push(b),
}
}
for entry in parsed_network_config.container_entry {
let ctr_networks = network_membership
.entry(entry.id.clone())
.or_insert_with(Vec::new);
if !ctr_networks.contains(&network_name) {
ctr_networks.push(network_name.clone());
}
let mut new_ctr_ips: Vec<IpAddr> = Vec::new();
if let Some(v4) = entry.v4 {
for ip in v4 {
reverse
.entry(network_name.clone())
.or_insert_with(HashMap::new)
.entry(IpAddr::V4(ip))
.or_insert_with(Vec::new)
.append(&mut entry.aliases.clone());
ctr_dns_server.insert(IpAddr::V4(ip), entry.dns_servers.clone());
new_ctr_ips.push(IpAddr::V4(ip));
}
}
if let Some(v6) = entry.v6 {
for ip in v6 {
reverse
.entry(network_name.clone())
.or_insert_with(HashMap::new)
.entry(IpAddr::V6(ip))
.or_insert_with(Vec::new)
.append(&mut entry.aliases.clone());
ctr_dns_server.insert(IpAddr::V6(ip), entry.dns_servers.clone());
new_ctr_ips.push(IpAddr::V6(ip));
}
}
let ctr_ips = container_ips
.entry(entry.id.clone())
.or_insert_with(Vec::new);
ctr_ips.append(&mut new_ctr_ips.clone());
let network_aliases = network_names
.entry(network_name.clone())
.or_insert_with(HashMap::new);
for alias in entry.aliases {
let alias_entries = network_aliases.entry(alias).or_insert_with(Vec::new);
alias_entries.append(&mut new_ctr_ips.clone());
}
}
}
Err(e) => warn!("Error reading config file for server update: {}", e),
}
}
let mut ctrs: HashMap<IpAddr, Vec<String>> = HashMap::new();
for (ctr_id, ips) in container_ips {
match network_membership.get(&ctr_id) {
Some(s) => {
for ip in ips {
let ip_networks = ctrs.entry(ip).or_insert_with(Vec::new);
ip_networks.append(&mut s.clone());
}
}
None => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"Container ID {} has an entry in IPs table, but not network membership table",
ctr_id
),
))
}
}
}
Ok((
DNSBackend::new(
ctrs,
network_names,
reverse,
ctr_dns_server,
network_dns_server,
),
listen_ips_4,
listen_ips_6,
))
}
struct CtrEntry {
id: String,
v4: Option<Vec<Ipv4Addr>>,
v6: Option<Vec<Ipv6Addr>>,
aliases: Vec<String>,
dns_servers: Option<Vec<IpAddr>>,
}
struct ParsedNetworkConfig {
network_bind_ip: Vec<IpAddr>,
container_entry: Vec<CtrEntry>,
network_dnsservers: Vec<IpAddr>,
}
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();
for line in content.split('\n') {
if line.is_empty() {
continue;
}
if is_first {
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()),
));
}
for ip in network_parts[0].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 ip address {}: {}", ip, e),
))
}
};
bind_addrs.push(local_ip);
}
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;
}
let parts = line.split(' ').collect::<Vec<&str>>();
if parts.len() < 4 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"configuration file {} line {} is improperly formatted - too few entries",
path.to_string_lossy(),
line
),
));
}
let v4_addrs: Option<Vec<Ipv4Addr>> = if !parts[1].is_empty() {
let ipv4 = match parts[1].split(',').map(|i| i.parse()).collect() {
Ok(i) => i,
Err(e) => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("error parsing IP address {}: {}", parts[1], e),
))
}
};
Some(ipv4)
} else {
None
};
let v6_addrs: Option<Vec<Ipv6Addr>> = if !parts[2].is_empty() {
let ipv6 = match parts[2].split(',').map(|i| i.parse()).collect() {
Ok(i) => i,
Err(e) => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("error parsing IP address {}: {}", parts[2], e),
))
}
};
Some(ipv6)
} else {
None
};
let aliases: Vec<String> = parts[3]
.split(',')
.map(|x| x.to_string().to_lowercase())
.collect::<Vec<String>>();
if aliases.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"configuration file {} line {} is improperly formatted - no names given",
path.to_string_lossy(),
line
),
));
}
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),
))
}
};
Some(dns_server)
} else {
None
};
ctrs.push(CtrEntry {
id: parts[0].to_string().to_lowercase(),
v4: v4_addrs,
v6: v6_addrs,
aliases,
dns_servers,
});
}
if bind_addrs.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"configuration file {} does not provide any bind addresses",
path.to_string_lossy()
),
));
}
Ok(ParsedNetworkConfig {
network_bind_ip: bind_addrs,
container_entry: ctrs,
network_dnsservers: network_dns_servers,
})
}