diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 4f78a523c..91ebafb53 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,4 @@ pub mod setup; pub mod teardown; +pub mod update; pub mod version; diff --git a/src/commands/update.rs b/src/commands/update.rs new file mode 100644 index 000000000..e1f31e4b1 --- /dev/null +++ b/src/commands/update.rs @@ -0,0 +1,80 @@ +use crate::dns::aardvark::Aardvark; +use crate::error::{NetavarkError, NetavarkErrorList, NetavarkResult}; + +use clap::Parser; +use log::debug; +use std::env; +use std::path::Path; + +#[derive(Parser, Debug)] +pub struct Update { + /// Network name to update + #[clap(forbid_empty_values = true, required = true)] + network_name: String, + /// DNS Servers to update for the network + #[clap(short, long, required = true, forbid_empty_values = true)] + network_dns_servers: Vec, +} + +impl Update { + /// Updates network dns servers for an already configured network + pub fn new(network_name: String, network_dns_servers: Vec) -> Self { + Self { + network_name, + network_dns_servers, + } + } + + pub fn exec( + &self, + config_dir: String, + aardvark_bin: String, + rootless: bool, + ) -> NetavarkResult<()> { + let mut error_list = NetavarkErrorList::new(); + let dns_port = match env::var("NETAVARK_DNS_PORT") { + Ok(port_string) => match port_string.parse() { + Ok(port) => port, + Err(e) => { + return Err(NetavarkError::Message(format!( + "Invalid NETAVARK_DNS_PORT {}: {}", + port_string, e + ))) + } + }, + Err(_) => 53, + }; + + println!( + "config {:?}, aardvarl_bind {:?}, rootless {:?}", + config_dir, aardvark_bin, rootless + ); + println!( + "network_name {:?}, network dns server {:?}", + &self.network_name, &self.network_dns_servers + ); + if Path::new(&aardvark_bin).exists() { + let path = Path::new(&config_dir).join("aardvark-dns"); + if let Ok(path_string) = path.into_os_string().into_string() { + let mut aardvark_interface = + Aardvark::new(path_string, rootless, aardvark_bin, dns_port); + if let Err(err) = aardvark_interface + .modify_network_dns_servers(&self.network_name, &self.network_dns_servers) + { + error_list.push(err.into()); + } + } else { + error_list.push(NetavarkError::msg( + "Unable to parse aardvark config directory", + )); + } + } + + if !error_list.is_empty() { + return Err(NetavarkError::List(error_list)); + } + + debug!("{:?}", "Update complete"); + Ok(()) + } +} diff --git a/src/dns/aardvark.rs b/src/dns/aardvark.rs index 748d0d97f..c58670d2d 100644 --- a/src/dns/aardvark.rs +++ b/src/dns/aardvark.rs @@ -327,6 +327,61 @@ impl Aardvark { Ok(()) } + // Modifies network dns_servers for a specific network and notifies aardvark-dns server + // with the change. + pub fn modify_network_dns_servers( + &mut self, + network_name: &str, + network_dns_servers: &Vec, + ) -> Result<()> { + let mut dns_servers_modified = false; + let path = Path::new(&self.config).join(network_name); + let file_content = fs::read_to_string(&path)?; + + let mut file = File::create(&path)?; + + //for line in lines { + for (idx, line) in file_content.split_terminator('\n').enumerate() { + let mut content = line.to_string(); + if idx == 0 { + // If this is first line, we have to modify this + // first line has a format of `... ..` + // We will read the first line and get the first coloumn and + // override the second coloumn with new network dns servers. + let network_parts = line.split(' ').collect::>(); + if network_parts.is_empty() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("invalid network configuration file: {}", path.display()), + )); + } + let network_dns_servers_collected = if !network_dns_servers.is_empty() { + dns_servers_modified = true; + let dns_server_collected = network_dns_servers + .iter() + .map(|g| g.to_string()) + .collect::>() + .join(","); + format!(" {}", dns_server_collected) + } else { + "".to_string() + }; + // Modify line to support new format + content = format!("{}{}", network_parts[0], network_dns_servers_collected); + } + file.write_all(content.as_bytes())?; + file.write_all(b"\n")?; + } + + // If dns servers were updated notify the aardvark-dns server + // if refresh is needed. + if dns_servers_modified { + self.notify(false)?; + } + + Ok(()) + } + pub fn delete_from_netavark_entries( &self, network_options: &types::NetworkOptions, diff --git a/src/main.rs b/src/main.rs index e161228e4..756abd00e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use clap::{Parser, Subcommand}; use netavark::commands::setup; use netavark::commands::teardown; +use netavark::commands::update; use netavark::commands::version; #[derive(Parser, Debug)] @@ -28,6 +29,8 @@ struct Opts { enum SubCommand { /// Configures the given network namespace with the given configuration. Setup(setup::Setup), + /// Updates network dns servers for an already configured network. + Update(update::Update), /// Undo any configuration applied via setup command. Teardown(teardown::Teardown), /// Display info about netavark. @@ -47,6 +50,7 @@ fn main() { let result = match opts.subcmd { SubCommand::Setup(setup) => setup.exec(opts.file, config, aardvark_bin, rootless), SubCommand::Teardown(teardown) => teardown.exec(opts.file, config, aardvark_bin, rootless), + SubCommand::Update(update) => update.exec(config, aardvark_bin, rootless), SubCommand::Version(version) => version.exec(), }; diff --git a/test/100-bridge-iptables.bats b/test/100-bridge-iptables.bats index 1e28e6d44..674590aa4 100644 --- a/test/100-bridge-iptables.bats +++ b/test/100-bridge-iptables.bats @@ -111,6 +111,47 @@ fw_driver=iptables expected_rc=1 run_in_host_netns ip addr show podman0 } +@test "$fw_driver - bridge driver must generate config for aardvark with multiple custom dns server with network dns servers and perform update" { + # get a random port directly to avoid low ports e.g. 53 would not create iptables + dns_port=$((RANDOM+10000)) + + # hack to make aardvark-dns run when really root or when running as user with + # podman unshare --rootless-netns; since netavark runs aardvark with systemd-run + # it needs to know if it should use systemd user instance or not. + # iptables are still setup identically. + rootless=false + if [[ ! -e "/run/dbus/system_bus_socket" ]]; then + rootless=true + fi + + mkdir -p "$NETAVARK_TMPDIR/config" + + NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ + --rootless "$rootless" --config "$NETAVARK_TMPDIR/config" \ + setup $(get_container_netns_path) + + # check aardvark config and running + run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" + assert "${lines[0]}" =~ "10.89.3.1,fd10:88:a::1 127.0.0.1,3.3.3.3" "aardvark set to listen to all IPs" + assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename 8.8.8.8,1.1.1.1$" "aardvark config's container" + assert "${#lines[@]}" = 2 "too many lines in aardvark config" + + aardvark_pid=$(cat "$NETAVARK_TMPDIR/config/aardvark-dns/aardvark.pid") + assert "$ardvark_pid" =~ "[0-9]*" "aardvark pid not found" + run_helper ps "$aardvark_pid" + assert "${lines[1]}" =~ ".*aardvark-dns --config $NETAVARK_TMPDIR/config/aardvark-dns -p $dns_port run" "aardvark not running or bad options" + + NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ + --rootless "$rootless" --config "$NETAVARK_TMPDIR/config" \ + update podman1 --network-dns-servers 8.8.8.8 + + # check aardvark config and running + run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" + assert "${lines[0]}" =~ "10.89.3.1,fd10:88:a::1 8.8.8.8" "aardvark set to listen to all IPs" + assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename 8.8.8.8,1.1.1.1$" "aardvark config's container" + assert "${#lines[@]}" = 2 "too many lines in aardvark config" +} + @test "$fw_driver - ipv6 bridge" { run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge.json setup $(get_container_netns_path) result="$output"