From f42d5651ce98a4f84b35a8c7eda626dee8ce0489 Mon Sep 17 00:00:00 2001 From: Mario Loriedo Date: Wed, 5 Nov 2025 15:53:46 +0100 Subject: [PATCH 1/3] Remove iptables support Code and documentation changes to remove the support of iptables as a firewall driver. Signed-off-by: Mario Loriedo --- Cargo.lock | 17 - Cargo.toml | 1 - README.md | 2 +- build.rs | 7 +- docs/netavark-firewalld.7.md | 2 +- rpm/netavark.spec | 17 +- src/firewall/fwnone.rs | 2 +- src/firewall/iptables.rs | 218 ------ src/firewall/mod.rs | 20 +- src/firewall/nft.rs | 2 + src/firewall/state.rs | 4 +- src/firewall/varktables/helpers.rs | 107 --- src/firewall/varktables/mod.rs | 2 - src/firewall/varktables/types.rs | 648 ---------------- src/network/bridge.rs | 2 +- src/network/types.rs | 2 +- test/100-bridge-iptables.bats | 1104 ---------------------------- test/200-bridge-firewalld.bats | 15 +- test/README.md | 3 +- test/helpers.bash | 1 - 20 files changed, 19 insertions(+), 2157 deletions(-) delete mode 100644 src/firewall/iptables.rs delete mode 100644 src/firewall/varktables/helpers.rs delete mode 100644 src/firewall/varktables/mod.rs delete mode 100644 src/firewall/varktables/types.rs delete mode 100644 test/100-bridge-iptables.bats diff --git a/Cargo.lock b/Cargo.lock index 5b7fdceaa..e720ea641 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -898,16 +898,6 @@ dependencies = [ "serde", ] -[[package]] -name = "iptables" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30c9a636a0a728c67d1d420471c99b215708a17c222bb9afb16d0821e2d80d8" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -963,12 +953,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" version = "0.2.177" @@ -1061,7 +1045,6 @@ dependencies = [ "futures-util", "hyper-util", "ipnet", - "iptables", "libc", "log", "mozim", diff --git a/Cargo.toml b/Cargo.toml index 854bea1dd..a43d83bc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,6 @@ anyhow = "1.0.93" clap = { version = "~4.5.51", features = ["derive", "env"] } env_logger = "0.11.8" ipnet = { version = "2.11.0", features = ["serde"] } -iptables = "0.6.0" libc = "0.2.157" log = "0.4.28" serde = { version = "1.0.228", features = ["derive"] } diff --git a/README.md b/README.md index 4c1541da9..0a44ce943 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Netavark is a tool for configuring networking for Linux containers. Its features * Configuration of container networks via JSON configuration file * Creation and management of required network interfaces, including MACVLAN networks * All required firewall configuration to perform NAT and port forwarding as required for containers -* Support for iptables, firewalld and nftables +* Support for firewalld and nftables * Support for rootless containers * Support for IPv4 and IPv6 * Support for container DNS resolution via the [aardvark-dns](https://github.com/containers/aardvark-dns) project diff --git a/build.rs b/build.rs index 3d5b79ba5..46592b104 100644 --- a/build.rs +++ b/build.rs @@ -64,17 +64,16 @@ fn main() { println!("cargo:rustc-env=GIT_COMMIT={commit}"); // Handle default firewall driver. - // Allowed values "nftables" and "iptables". + // Allowed values "nftables" and "none". let fwdriver = match env::var("NETAVARK_DEFAULT_FW") - .unwrap_or("iptables".to_string()) + .unwrap_or("nftables".to_string()) .as_str() { "nftables" => "nftables", - "iptables" => "iptables", "none" => "none", inv => panic!("Invalid default firewall driver {inv}"), }; - println!("cargo:rustc-check-cfg=cfg(default_fw, values(\"nftables\", \"iptables\", \"none\"))"); + println!("cargo:rustc-check-cfg=cfg(default_fw, values(\"nftables\", \"none\"))"); println!("cargo:rustc-cfg=default_fw=\"{fwdriver}\""); println!("cargo:rustc-env=DEFAULT_FW={fwdriver}"); } diff --git a/docs/netavark-firewalld.7.md b/docs/netavark-firewalld.7.md index 81c501ac4..43277ffa6 100644 --- a/docs/netavark-firewalld.7.md +++ b/docs/netavark-firewalld.7.md @@ -9,7 +9,7 @@ netavark-firewalld - description of the interaction of Netavark and firewalld ## Description Netavark can be used on systems with firewalld enabled without issue. -When using the default `nftables` or `iptables` firewall drivers, on systems where firewalld is running, firewalld will automatically be configured to allow connectivity to Podman containers. +When using the default `nftables` firewall driver, on systems where firewalld is running, firewalld will automatically be configured to allow connectivity to Podman containers. All subnets of Podman-managed networks will be automatically added to the `trusted` zone to allow this access. ### Firewalld Driver diff --git a/rpm/netavark.spec b/rpm/netavark.spec index f7795a123..c4267c2cc 100644 --- a/rpm/netavark.spec +++ b/rpm/netavark.spec @@ -14,15 +14,6 @@ # Minimum X.Y dep for aardvark-dns %define major_minor %((v=%{version}; echo ${v%.*})) -# Set default firewall to nftables on CentOS Stream 10+, RHEL 10+, Fedora 41+ -# and default to iptables on all other environments -# The `rhel` macro is defined on CentOS Stream, RHEL as well as Fedora ELN. -%if (%{defined rhel} && 0%{?rhel} >= 10) || (%{defined fedora} && 0%{?fedora} >= 41) -%define default_fw nftables -%else -%define default_fw iptables -%endif - Name: netavark # Set a different Epoch for copr builds %if %{defined copr_username} @@ -49,11 +40,7 @@ BuildRequires: %{_bindir}/go-md2man # aardvark-dns and %%{name} are usually released in sync Requires: aardvark-dns >= %{epoch}:%{major_minor} Provides: container-network-stack = 2 -%if "%{default_fw}" == "nftables" Requires: nftables -%else -Requires: iptables -%endif BuildRequires: make BuildRequires: protobuf-c BuildRequires: protobuf-compiler @@ -82,7 +69,7 @@ Its features include: including MACVLAN networks * All required firewall configuration to perform NAT and port forwarding as required for containers -* Support for iptables, firewalld and nftables +* Support for firewalld and nftables * Support for rootless containers * Support for IPv4 and IPv6 * Support for container DNS resolution via aardvark-dns. @@ -102,7 +89,7 @@ tar fx %{SOURCE1} %endif %build -NETAVARK_DEFAULT_FW=%{default_fw} %{__make} CARGO="%{__cargo}" build +%{__make} CARGO="%{__cargo}" build %if (0%{?fedora} || 0%{?rhel} >= 10) && !%{defined copr_username} %cargo_license_summary %{cargo_license} > LICENSE.dependencies diff --git a/src/firewall/fwnone.rs b/src/firewall/fwnone.rs index 8c603e280..47ae30152 100644 --- a/src/firewall/fwnone.rs +++ b/src/firewall/fwnone.rs @@ -4,7 +4,7 @@ use crate::network::internal_types::{ PortForwardConfig, SetupNetwork, TearDownNetwork, TeardownPortForward, }; -// Iptables driver - uses direct iptables commands via the iptables crate. +// Fwnone driver - No-op firewall implementation. pub struct Fwnone {} pub fn new() -> NetavarkResult> { diff --git a/src/firewall/iptables.rs b/src/firewall/iptables.rs deleted file mode 100644 index 817ca79ca..000000000 --- a/src/firewall/iptables.rs +++ /dev/null @@ -1,218 +0,0 @@ -use crate::error::{NetavarkError, NetavarkResult}; -use crate::firewall; -use crate::firewall::firewalld; -use crate::firewall::varktables::types::TeardownPolicy::OnComplete; -use crate::firewall::varktables::types::{ - create_network_chains, get_network_chains, get_port_forwarding_chains, TeardownPolicy, -}; -use crate::network::internal_types::{ - PortForwardConfig, SetupNetwork, TearDownNetwork, TeardownPortForward, -}; -use iptables; -use iptables::IPTables; - -pub(crate) const MAX_HASH_SIZE: usize = 13; - -// Iptables driver - uses direct iptables commands via the iptables crate. -pub struct IptablesDriver { - conn: IPTables, - conn6: IPTables, -} - -pub fn new() -> NetavarkResult> { - // create an iptables connection - let ipt = match iptables::new(false) { - Ok(i) => i, - Err(e) => return Err(NetavarkError::Message(format!("iptables: {e}"))), - }; - let ipt6 = match iptables::new(true) { - Ok(i) => i, - Err(e) => return Err(NetavarkError::Message(format!("ip6tables: {e}"))), - }; - let driver = IptablesDriver { - conn: ipt, - conn6: ipt6, - }; - Ok(Box::new(driver)) -} - -impl firewall::FirewallDriver for IptablesDriver { - fn driver_name(&self) -> &str { - firewall::IPTABLES - } - - fn setup_network( - &self, - network_setup: SetupNetwork, - dbus_con: &Option, - ) -> NetavarkResult<()> { - if let Some(subnet) = network_setup.subnets { - for network in subnet { - let is_ipv6 = network.network().is_ipv6(); - let mut conn = &self.conn; - if is_ipv6 { - conn = &self.conn6; - } - - let chains = get_network_chains( - conn, - network, - &network_setup.network_hash_name, - is_ipv6, - network_setup.bridge_name.clone(), - network_setup.isolation, - network_setup.dns_port, - ); - - create_network_chains(chains)?; - - firewalld::add_firewalld_if_possible(dbus_con, &network); - } - } - Ok(()) - } - - // teardown_network should only be called in the case of - // a complete teardown. - fn teardown_network(&self, tear: TearDownNetwork) -> NetavarkResult<()> { - // Remove network specific general NAT rules - if let Some(subnet) = tear.config.subnets { - for network in subnet { - let is_ipv6 = network.network().is_ipv6(); - let mut conn = &self.conn; - if is_ipv6 { - conn = &self.conn6; - } - let chains = get_network_chains( - conn, - network, - &tear.config.network_hash_name, - is_ipv6, - tear.config.bridge_name.clone(), - tear.config.isolation, - tear.config.dns_port, - ); - - for c in &chains { - c.remove_rules(tear.complete_teardown)?; - } - for c in chains { - match &c.td_policy { - None => {} - Some(policy) => { - if tear.complete_teardown && *policy == OnComplete { - c.remove()?; - } - } - } - } - - if tear.complete_teardown { - firewalld::rm_firewalld_if_possible(&network) - } - } - } - Result::Ok(()) - } - - fn setup_port_forward( - &self, - setup_portfw: PortForwardConfig, - dbus_con: &Option, - ) -> NetavarkResult<()> { - firewalld::check_can_forward_ports(dbus_con, &setup_portfw)?; - - if let Some(v4) = setup_portfw.container_ip_v4 { - let subnet_v4 = match setup_portfw.subnet_v4 { - Some(s) => s, - None => { - return Err(NetavarkError::msg( - "ipv4 address but provided but no v4 subnet provided", - )) - } - }; - let chains = - get_port_forwarding_chains(&self.conn, &setup_portfw, &v4, &subnet_v4, false)?; - create_network_chains(chains)?; - } - if let Some(v6) = setup_portfw.container_ip_v6 { - let subnet_v6 = match setup_portfw.subnet_v6 { - Some(s) => s, - None => { - return Err(NetavarkError::msg( - "ipv6 address but provided but no v6 subnet provided", - )) - } - }; - let chains = - get_port_forwarding_chains(&self.conn6, &setup_portfw, &v6, &subnet_v6, true)?; - create_network_chains(chains)?; - }; - Result::Ok(()) - } - - fn teardown_port_forward(&self, tear: TeardownPortForward) -> NetavarkResult<()> { - if let Some(v4) = tear.config.container_ip_v4 { - let subnet_v4 = match tear.config.subnet_v4 { - Some(s) => s, - None => { - return Err(NetavarkError::msg( - "ipv4 address but provided but no v4 subnet provided", - )) - } - }; - - let chains = - get_port_forwarding_chains(&self.conn, &tear.config, &v4, &subnet_v4, false)?; - - for chain in &chains { - chain.remove_rules(tear.complete_teardown)?; - } - for chain in &chains { - if !tear.complete_teardown || !chain.create { - continue; - } - match &chain.td_policy { - None => {} - Some(policy) => { - if *policy == TeardownPolicy::OnComplete { - chain.remove()?; - } - } - } - } - } - - if let Some(v6) = tear.config.container_ip_v6 { - let subnet_v6 = match tear.config.subnet_v6 { - Some(s) => s, - None => { - return Err(NetavarkError::msg( - "ipv6 address but provided but no v6 subnet provided", - )) - } - }; - - let chains = - get_port_forwarding_chains(&self.conn6, &tear.config, &v6, &subnet_v6, true)?; - - for chain in &chains { - chain.remove_rules(tear.complete_teardown)?; - } - for chain in &chains { - if !tear.complete_teardown || !chain.create { - continue; - } - match &chain.td_policy { - None => {} - Some(policy) => { - if *policy == TeardownPolicy::OnComplete { - chain.remove()?; - } - } - } - } - } - Result::Ok(()) - } -} diff --git a/src/firewall/mod.rs b/src/firewall/mod.rs index 0f185eedd..9382b4ce7 100644 --- a/src/firewall/mod.rs +++ b/src/firewall/mod.rs @@ -7,12 +7,9 @@ use zbus::blocking::Connection; pub mod firewalld; pub mod fwnone; -pub mod iptables; pub mod nft; pub mod state; -mod varktables; -const IPTABLES: &str = "iptables"; const FIREWALLD: &str = "firewalld"; const NFTABLES: &str = "nftables"; const NONE: &str = "none"; @@ -44,7 +41,6 @@ pub trait FirewallDriver { /// Types of firewall backend enum FirewallImpl { - Iptables, Firewalld(Connection), Nftables, Fwnone, @@ -52,7 +48,7 @@ enum FirewallImpl { /// What firewall implementations does this system support? fn get_firewall_impl(driver_name: Option) -> NetavarkResult { - // It respects "firewalld", "iptables", "nftables", "none". + // It respects "firewalld", "nftables", "none". if let Some(driver) = driver_name { debug!("Forcibly using firewall driver {driver}"); match driver.to_lowercase().as_str() { @@ -68,7 +64,6 @@ fn get_firewall_impl(driver_name: Option) -> NetavarkResult return Ok(FirewallImpl::Iptables), NFTABLES => return Ok(FirewallImpl::Nftables), NONE => return Ok(FirewallImpl::Fwnone), any => { @@ -84,7 +79,7 @@ fn get_firewall_impl(driver_name: Option) -> NetavarkResult conn, - // Err(_) => return FirewallImpl::Iptables, + // Err(_) => return FirewallImpl::Nftables, // }; // match conn.call_method( // Some("org.freedesktop.DBus"), @@ -94,7 +89,7 @@ fn get_firewall_impl(driver_name: Option) -> NetavarkResult FirewallImpl::Firewalld(conn), - // Err(_) => FirewallImpl::Iptables, + // Err(_) => FirewallImpl::Nftables, // } } @@ -103,11 +98,6 @@ fn get_default_fw_impl() -> NetavarkResult { Ok(FirewallImpl::Nftables) } -#[cfg(default_fw = "iptables")] -fn get_default_fw_impl() -> NetavarkResult { - Ok(FirewallImpl::Iptables) -} - #[cfg(default_fw = "none")] fn get_default_fw_impl() -> NetavarkResult { Ok(FirewallImpl::Fwnone) @@ -120,10 +110,6 @@ pub fn get_supported_firewall_driver( ) -> NetavarkResult> { match get_firewall_impl(driver_name) { Ok(fw) => match fw { - FirewallImpl::Iptables => { - info!("Using iptables firewall driver"); - iptables::new() - } FirewallImpl::Firewalld(conn) => { info!("Using firewalld firewall driver"); firewalld::new(conn) diff --git a/src/firewall/nft.rs b/src/firewall/nft.rs index 3eba30a3a..c792ccea1 100644 --- a/src/firewall/nft.rs +++ b/src/firewall/nft.rs @@ -29,6 +29,8 @@ const ISOLATION1CHAIN: &str = "NETAVARK-ISOLATION-1"; const ISOLATION2CHAIN: &str = "NETAVARK-ISOLATION-2"; const ISOLATION3CHAIN: &str = "NETAVARK-ISOLATION-3"; +pub(crate) const MAX_HASH_SIZE: usize = 13; + const MASK: u32 = 0x2000; /// The dnat priority for chains diff --git a/src/firewall/state.rs b/src/firewall/state.rs index 6165f462c..a2736cbdf 100644 --- a/src/firewall/state.rs +++ b/src/firewall/state.rs @@ -259,7 +259,7 @@ mod tests { fn test_fw_config() { let network_id = "abc"; let container_id = "123"; - let driver = "iptables"; + let driver = "nftables"; let tmpdir = Builder::new().prefix("netavark-tests").tempdir().unwrap(); let config_dir = tmpdir.path(); @@ -306,7 +306,7 @@ mod tests { drop(paths.lock_file); // unlock to prevent deadlock with other calls let res = fs::read_to_string(paths.fw_driver_file).unwrap(); - assert_eq!(res, "iptables", "read fw driver"); + assert_eq!(res, "nftables", "read fw driver"); let res = fs::read_to_string(&paths.net_conf_file).unwrap(); assert_eq!(res, net_conf_json, "read net conf"); diff --git a/src/firewall/varktables/helpers.rs b/src/firewall/varktables/helpers.rs deleted file mode 100644 index 4e4d10db5..000000000 --- a/src/firewall/varktables/helpers.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::error::{NetavarkError, NetavarkResult}; -use iptables::IPTables; -use log::debug; - -// append a rule to chain if it does not exist -// Note: While there is an API provided for this exact thing, the API returns -// an error that is not defined if the rule exists. This function just returns -// an error if there is a problem. -pub fn append_unique( - driver: &IPTables, - table: &str, - chain: &str, - rule: &str, -) -> NetavarkResult<()> { - let exists = match driver.exists(table, chain, rule) { - Ok(b) => b, - Err(e) => return Err(NetavarkError::Message(e.to_string())), - }; - if exists { - debug_rule_exists(table, chain, rule.to_string()); - return Ok(()); - } - if let Err(e) = driver - .append(table, chain, rule) - .map(|_| debug_rule_create(table, chain, rule.to_string())) - { - return Err(NetavarkError::Message(format!( - "unable to append rule '{rule}' to table '{table}': {e}", - ))); - } - Result::Ok(()) -} - -// add a chain if it does not exist, else do nothing -pub fn add_chain_unique(driver: &IPTables, table: &str, new_chain: &str) -> NetavarkResult<()> { - // Note: while there is an API provided to check if a chain exists in a table - // by iptables, it, for some reason, is slow. Instead we just get a list of - // chains in a table and iterate. Same is being done in golang implementations - let exists = chain_exists(driver, table, new_chain)?; - if exists { - debug_chain_exists(table, new_chain); - return Ok(()); - } - match driver - .new_chain(table, new_chain) - .map(|_| debug_chain_create(table, new_chain)) - { - Ok(_) => Ok(()), - Err(e) => Err(NetavarkError::Message(e.to_string())), - } -} - -// returns a bool as to whether the chain exists -fn chain_exists(driver: &IPTables, table: &str, chain: &str) -> NetavarkResult { - let c = match driver.list_chains(table) { - Ok(b) => b, - Err(e) => return Err(NetavarkError::Message(e.to_string())), - }; - if c.iter().any(|i| i == chain) { - debug_chain_exists(table, chain); - return Ok(true); - } - Ok(false) -} - -pub fn remove_if_rule_exists( - driver: &IPTables, - table: &str, - chain: &str, - rule: &str, -) -> NetavarkResult<()> { - // If the rule is not present, do not error - let exists = match driver.exists(table, chain, rule) { - Ok(b) => b, - Err(e) => return Err(NetavarkError::Message(e.to_string())), - }; - if !exists { - debug_rule_no_exists(table, chain, rule.to_string()); - return Ok(()); - } - if let Err(e) = driver.delete(table, chain, rule) { - return Err(NetavarkError::Message(format!( - "failed to remove rule '{rule}' from table '{chain}': {e}" - ))); - } - Result::Ok(()) -} - -fn debug_chain_create(table: &str, chain: &str) { - debug!("chain {chain} created on table {table}"); -} - -fn debug_chain_exists(table: &str, chain: &str) { - debug!("chain {chain} exists on table {table}"); -} - -pub fn debug_rule_create(table: &str, chain: &str, rule: String) { - debug!("rule {rule} created on table {table} and chain {chain}"); -} - -fn debug_rule_exists(table: &str, chain: &str, rule: String) { - debug!("rule {rule} exists on table {table} and chain {chain}"); -} - -fn debug_rule_no_exists(table: &str, chain: &str, rule: String) { - debug!("no rule {rule} exists on table {table} and chain {chain}"); -} diff --git a/src/firewall/varktables/mod.rs b/src/firewall/varktables/mod.rs deleted file mode 100644 index 2c0397931..000000000 --- a/src/firewall/varktables/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub(crate) mod helpers; -pub(crate) mod types; diff --git a/src/firewall/varktables/types.rs b/src/firewall/varktables/types.rs deleted file mode 100644 index 2c28ca567..000000000 --- a/src/firewall/varktables/types.rs +++ /dev/null @@ -1,648 +0,0 @@ -use crate::error::{NetavarkError, NetavarkResult}; -use crate::firewall::varktables::helpers::{ - add_chain_unique, append_unique, remove_if_rule_exists, -}; -use crate::firewall::varktables::types::TeardownPolicy::{Never, OnComplete}; -use crate::network::internal_types::{IsolateOption, PortForwardConfig}; -use ipnet::IpNet; -use iptables::IPTables; -use log::debug; -use std::net::IpAddr; - -// Chain names -const NAT: &str = "nat"; -const FILTER: &str = "filter"; -const POSTROUTING: &str = "POSTROUTING"; -const PREROUTING: &str = "PREROUTING"; -const NETAVARK_FORWARD: &str = "NETAVARK_FORWARD"; -const NETAVARK_FIREWALL_RULE_BUILDER: &str = "-m comment --comment 'netavark firewall rules' -j "; -const NETAVARK_INPUT: &str = "NETAVARK_INPUT"; -const OUTPUT: &str = "OUTPUT"; -const INPUT: &str = "INPUT"; -const FORWARD: &str = "FORWARD"; -const ACCEPT: &str = "ACCEPT"; -const NETAVARK_HOSTPORT_DNAT: &str = "NETAVARK-HOSTPORT-DNAT"; -const NETAVARK_HOSTPORT_SETMARK: &str = "NETAVARK-HOSTPORT-SETMARK"; -const NETAVARK_HOSTPORT_MASK: &str = "NETAVARK-HOSTPORT-MASQ"; -const MASQUERADE: &str = "MASQUERADE"; -const MARK: &str = "MARK"; -const DNAT: &str = "DNAT"; -const NETAVARK_ISOLATION_1: &str = "NETAVARK_ISOLATION_1"; -const NETAVARK_ISOLATION_2: &str = "NETAVARK_ISOLATION_2"; -const NETAVARK_ISOLATION_3: &str = "NETAVARK_ISOLATION_3"; - -const CONTAINER_DN_CHAIN: &str = "NETAVARK-DN-"; - -const HEXMARK: &str = "0x2000"; - -const MULTICAST_NET_V4: &str = "224.0.0.0/4"; -const MULTICAST_NET_V6: &str = "ff00::/8"; - -#[derive(PartialEq, Eq, Debug, Clone)] -pub enum TeardownPolicy { - OnComplete, - Never, -} - -#[derive(Clone, Debug)] -pub struct VarkRule { - // Formatted string of the rule itself - pub rule: String, - pub td_policy: Option, - /// position can be set to specify the exact rule position, - /// if None the rule will be appended. - pub position: Option, -} - -impl VarkRule { - fn new(rule: String, policy: Option) -> VarkRule { - VarkRule { - rule, - td_policy: policy, - position: None, - } - } - fn to_str(&self) -> &str { - &self.rule - } -} -// Varkchain is an iptable chain with extra info -pub struct VarkChain<'a> { - // name of chain - pub chain_name: String, - // should the chain be created by us - pub create: bool, - // the connection to iptables, v4 or v6 - pub driver: &'a IPTables, - // an array of iptables rules to be added to the chain - pub rules: Vec, - // name of table - pub table: String, - // if the chain should be removed - pub td_policy: Option, -} - -impl VarkChain<'_> { - fn new( - driver: &IPTables, - table: String, - chain_name: String, - td_policy: Option, - ) -> VarkChain<'_> { - VarkChain { - driver, - chain_name, - table, - rules: vec![], - create: false, - td_policy, - } - } - - // create a queue of rules in a vector - fn build_rule(&mut self, rule: VarkRule) { - self.rules.push(rule) - } - - // actually add the rules to iptables - pub fn add_rules(&self) -> NetavarkResult<()> { - for rule in &self.rules { - // If the rule comes with an optional position, then instead of append - // we should use insert if it does not already exist - match rule.position { - None => { - append_unique(self.driver, &self.table, &self.chain_name, rule.to_str())?; - } - Some(pos) => { - let exists = match self - .driver - .exists(&self.table, &self.chain_name, &rule.rule) - { - Ok(b) => b, - Err(e) => return Err(NetavarkError::Message(e.to_string())), - }; - if !exists { - match self - .driver - .insert(&self.table, &self.chain_name, &rule.rule, pos) - { - Ok(_) => {} - Err(e) => return Err(NetavarkError::Message(e.to_string())), - }; - } - } - } - } - Ok(()) - } - - // remove a vector of rules - pub fn remove_rules(&self, complete_teardown: bool) -> NetavarkResult<()> { - for rule in &self.rules { - // If the rule policy is Never or this is not a - // complete teardown of the network, then we skip removal - // of the rule - match &rule.td_policy { - None => {} - Some(policy) => { - if *policy == TeardownPolicy::Never || !complete_teardown { - continue; - } - } - } - remove_if_rule_exists(self.driver, &self.table, &self.chain_name, rule.to_str())?; - } - Ok(()) - } - - // remove the chain itself. - pub fn remove(&self) -> NetavarkResult<()> { - // this might be a perf hit but we are going to start this - // way and think of faster AND logical approach. - let remaining_rules = match self.driver.list(&self.table, &self.chain_name) { - Ok(o) => o, - Err(e) => return Err(NetavarkError::Message(e.to_string())), - }; - - // if for some reason there is a rule left, dont remove the chain and - // also dont make this a fatal error. The vec returned by list always - // reserves [0] for the chain name (-A chain_name), hence the <= 1 - if remaining_rules.len() <= 1 { - match self.driver.delete_chain(&self.table, &self.chain_name) { - Ok(_) => {} - Err(e) => return Err(NetavarkError::Message(e.to_string())), - }; - } - Result::Ok(()) - } -} - -pub fn create_network_chains(chains: Vec>) -> NetavarkResult<()> { - // we have to create first all chains because some might be referenced by other rules - // and this will fail if they do not exist yet - for c in &chains { - // If the chain needs to be created, we make it - if c.create { - add_chain_unique(c.driver, &c.table, &c.chain_name)?; - } - } - for c in &chains { - c.add_rules()? - } - Ok(()) -} - -pub fn get_network_chains<'a>( - conn: &'a IPTables, - network: IpNet, - network_hash_name: &'a str, - is_ipv6: bool, - interface_name: String, - isolation: IsolateOption, - dns_port: u16, -) -> Vec> { - let mut chains = Vec::new(); - let prefixed_network_hash_name = format!("{}-{}", "NETAVARK", network_hash_name); - - // NETAVARK-HASH - let mut hashed_network_chain = VarkChain::new( - conn, - NAT.to_string(), - prefixed_network_hash_name.clone(), - Some(OnComplete), - ); - hashed_network_chain.create = true; - - hashed_network_chain.build_rule(VarkRule::new( - format!("-d {network} -j {ACCEPT}"), - Some(TeardownPolicy::OnComplete), - )); - - let mut multicast_dest = MULTICAST_NET_V4; - if is_ipv6 { - multicast_dest = MULTICAST_NET_V6; - } - hashed_network_chain.build_rule(VarkRule::new( - format!("! -d {multicast_dest} -j {MASQUERADE}"), - Some(TeardownPolicy::OnComplete), - )); - chains.push(hashed_network_chain); - - // POSTROUTING - let mut postrouting_chain = - VarkChain::new(conn, NAT.to_string(), POSTROUTING.to_string(), None); - postrouting_chain.build_rule(VarkRule::new( - format!("-s {network} -j {prefixed_network_hash_name}"), - Some(TeardownPolicy::OnComplete), - )); - chains.push(postrouting_chain); - - // FORWARD chain - let mut forward_chain: VarkChain<'_> = - VarkChain::new(conn, FILTER.to_string(), FORWARD.to_string(), None); - - // INPUT chain - let mut input_chain: VarkChain<'_> = - VarkChain::new(conn, FILTER.to_string(), INPUT.to_string(), None); - - // used to prepend specific rules - let mut ind = 1; - - // NETAVARK_ISOLATION_2 - // NETAVARK_ISOLATION_2 chain must always exist, - // because non-isolation creates DROP rule in NETAVARK_ISOLATION_3 - // and NETAVARK_ISOLATION_3 references this as a jump target. - let mut netavark_isolation_chain_2 = VarkChain::new( - conn, - FILTER.to_string(), - NETAVARK_ISOLATION_2.to_string(), - None, - ); - netavark_isolation_chain_2.create = true; - - // NETAVARK_ISOLATION_3 - // NETAVARK_ISOLATION_3 chain must exist when IsolateOption is Never or Strict. - // bacause non-isolation creates DROP rule in NETAVARK_ISOLATION_3. - // and strict isolation references NETAVARK_ISOLATION_3 as a jump target. - let mut netavark_isolation_chain_3 = VarkChain::new( - conn, - FILTER.to_string(), - NETAVARK_ISOLATION_3.to_string(), - None, - ); - netavark_isolation_chain_3.create = true; - - if let IsolateOption::Normal | IsolateOption::Strict = isolation { - debug!("Add extra isolate rules"); - // NETAVARK_ISOLATION_1 - let mut netavark_isolation_chain_1 = VarkChain::new( - conn, - FILTER.to_string(), - NETAVARK_ISOLATION_1.to_string(), - None, - ); - netavark_isolation_chain_1.create = true; - - // -A FORWARD -j NETAVARK_ISOLATION_1 - forward_chain.build_rule(VarkRule { - rule: format!("-j {NETAVARK_ISOLATION_1}"), - position: Some(ind), - td_policy: Some(TeardownPolicy::OnComplete), - }); - - let netavark_isolation_1_target = if let IsolateOption::Strict = isolation { - // NETAVARK_ISOLATION_1 -i bridge_name ! -o bridge_name -j NETAVARK_ISOLATION_3 - NETAVARK_ISOLATION_3 - } else { - // NETAVARK_ISOLATION_1 -i bridge_name ! -o bridge_name -j NETAVARK_ISOLATION_2 - NETAVARK_ISOLATION_2 - }; - netavark_isolation_chain_1.build_rule(VarkRule { - rule: format!( - "-i {interface_name} ! -o {interface_name} -j {netavark_isolation_1_target}" - ), - position: Some(ind), - td_policy: Some(TeardownPolicy::OnComplete), - }); - - // NETAVARK_ISOLATION_2 -o bridge_name -j DROP - netavark_isolation_chain_2.build_rule(VarkRule { - rule: format!("-o {} -j {}", interface_name, "DROP"), - position: Some(ind), - td_policy: Some(TeardownPolicy::OnComplete), - }); - - // NETAVARK_ISOLATION_3 -j NETAVARK_ISOLATION_2 - netavark_isolation_chain_3.build_rule(VarkRule { - rule: format!("-j {NETAVARK_ISOLATION_2}"), - position: Some(ind), - td_policy: Some(TeardownPolicy::Never), - }); - - ind += 1; - - // PUSH CHAIN - chains.push(netavark_isolation_chain_1); - } else { - // create DROP rule for non-isolations to enforce strict isolation rules. - - // NETAVARK_ISOLATION_3 -o bridge_name -j DROP - netavark_isolation_chain_3.build_rule(VarkRule { - rule: format!("-o {} -j {}", interface_name, "DROP"), - position: Some(ind), - td_policy: Some(TeardownPolicy::OnComplete), - }); - - // NETAVARK_ISOLATION_3 -j NETAVARK_ISOLATION_2 - netavark_isolation_chain_3.build_rule(VarkRule { - rule: format!("-j {NETAVARK_ISOLATION_2}"), - // position +1 to place this rule under all of NETAVARK_ISOLATION_3 DROP rules. - position: Some(ind + 1), - td_policy: Some(TeardownPolicy::Never), - }); - } - - // PUSH CHAIN - chains.push(netavark_isolation_chain_2); - chains.push(netavark_isolation_chain_3); - - forward_chain.build_rule(VarkRule { - rule: format!("{NETAVARK_FIREWALL_RULE_BUILDER} {NETAVARK_FORWARD}"), - position: Some(ind), - td_policy: Some(TeardownPolicy::Never), - }); - chains.push(forward_chain); - - // NETAVARK_FORWARD - let mut netavark_forward_chain = - VarkChain::new(conn, FILTER.to_string(), NETAVARK_FORWARD.to_string(), None); - netavark_forward_chain.create = true; - - // Add NETAVARK_INPUT chain to INPUT chain - input_chain.build_rule(VarkRule { - rule: format!("{NETAVARK_FIREWALL_RULE_BUILDER} {NETAVARK_INPUT}"), - position: Some(1), - td_policy: Some(TeardownPolicy::Never), - }); - chains.push(input_chain); - - // NETAVARK_INPUT - let mut netavark_input_chain = - VarkChain::new(conn, FILTER.to_string(), NETAVARK_INPUT.to_string(), None); - netavark_input_chain.create = true; - - // Always add ACCEPT rules in firewall for dns traffic from containers - // to gateway when using bridge network with internal dns. - for proto in ["udp", "tcp"] { - netavark_input_chain.build_rule(VarkRule::new( - format!("-p {proto} -s {network} --dport {dns_port} -j {ACCEPT}"), - Some(TeardownPolicy::OnComplete), - )); - } - chains.push(netavark_input_chain); - - // Drop all invalid packages, due a race the container source ip could be leaked on the local - // network and we should avoid that, https://bugzilla.redhat.com/show_bug.cgi?id=2230144 - // This should't harm anything so just add one global rule instead of filtering per subnet. - netavark_forward_chain.build_rule(VarkRule::new( - "-m conntrack --ctstate INVALID -j DROP".to_string(), - Some(TeardownPolicy::Never), - )); - - // Create incoming traffic rule - // CNI did this by IP address, this is implemented per subnet - netavark_forward_chain.build_rule(VarkRule::new( - format!("-d {network} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT"), - Some(TeardownPolicy::OnComplete), - )); - - // Create outgoing traffic rule - // CNI did this by IP address, this is implemented per subnet - netavark_forward_chain.build_rule(VarkRule::new( - format!("-s {network} -j ACCEPT"), - Some(TeardownPolicy::OnComplete), - )); - chains.push(netavark_forward_chain); - - chains -} - -pub fn get_port_forwarding_chains<'a>( - conn: &'a IPTables, - pfwd: &PortForwardConfig, - container_ip: &IpAddr, - network_address: &IpNet, - is_ipv6: bool, -) -> NetavarkResult>> { - let mut localhost_ip = "127.0.0.1"; - if is_ipv6 { - localhost_ip = "::1"; - } - let mut chains = Vec::new(); - // Set up all chains - let network_dn_chain_name = CONTAINER_DN_CHAIN.to_owned() + &pfwd.network_hash_name; - - let comment_dn_network_cid = format!( - "-m comment --comment 'dnat name: {} id: {}'", - pfwd.network_name, pfwd.container_id - ); - - // // NETAVARK-HASH - - // NETAVARK-DN-HASH - let mut netavark_hashed_dn_chain = VarkChain::new( - conn, - NAT.to_string(), - CONTAINER_DN_CHAIN.to_string() + &pfwd.network_hash_name, - Some(OnComplete), - ); - - // NETAVARK_HOSTPORT_DNAT - // We need to create that chain for prerouting/output chain rules - // using it, even if there are no port mappings. - let mut netavark_hostport_dn_chain = VarkChain::new( - conn, - NAT.to_string(), - NETAVARK_HOSTPORT_DNAT.to_string(), - None, - ); - netavark_hostport_dn_chain.create = true; - - // Setup one-off rules that have nothing to do with ports - // PREROUTING - let mut prerouting_chain = VarkChain::new(conn, NAT.to_string(), PREROUTING.to_string(), None); - prerouting_chain.build_rule(VarkRule::new( - format!("-j {NETAVARK_HOSTPORT_DNAT} -m addrtype --dst-type LOCAL"), - Some(TeardownPolicy::Never), - )); - - // OUTPUT - let mut output_chain = VarkChain::new(conn, NAT.to_string(), OUTPUT.to_string(), None); - output_chain.build_rule(VarkRule::new( - format!("-j {NETAVARK_HOSTPORT_DNAT} -m addrtype --dst-type LOCAL"), - Some(TeardownPolicy::Never), - )); - - // NETAVARK-HOSTPORT-SETMARK - let mut netavark_hostport_setmark = VarkChain::new( - conn, - NAT.to_string(), - NETAVARK_HOSTPORT_SETMARK.to_string(), - None, - ); - netavark_hostport_setmark.create = true; - netavark_hostport_setmark.build_rule(VarkRule::new( - format!("-j {MARK} --set-xmark {HEXMARK}/{HEXMARK}"), - Some(TeardownPolicy::Never), - )); - chains.push(netavark_hostport_setmark); - - // NETAVARK-HOSTPORT-MASQ - let mut netavark_hostport_masq_chain = VarkChain::new( - conn, - NAT.to_string(), - NETAVARK_HOSTPORT_MASK.to_string(), - None, - ); - netavark_hostport_masq_chain.create = true; - netavark_hostport_masq_chain.build_rule(VarkRule::new( - format!( - "-j {MASQUERADE} -m comment --comment 'netavark portfw masq mark' -m mark --mark {HEXMARK}/{HEXMARK}" - ), - Some(TeardownPolicy::Never), - )); - netavark_hostport_masq_chain.create = true; - chains.push(netavark_hostport_masq_chain); - - // POSTROUTING - let mut postrouting = VarkChain::new(conn, NAT.to_string(), POSTROUTING.to_string(), None); - // This rule must be in the first position - postrouting.build_rule(VarkRule { - rule: format!("-j {NETAVARK_HOSTPORT_MASK} "), - position: Some(1), - td_policy: Some(Never), - }); - - chains.push(postrouting); - - // Determine if we need to create chains - if pfwd.port_mappings.is_some() { - netavark_hashed_dn_chain.create = true; - } - - // Create redirection for aardvark-dns on non-standard port - if pfwd.dns_port != 53 { - for dns_ip in pfwd.dns_server_ips { - if is_ipv6 != dns_ip.is_ipv6() { - continue; - } - let mut ip_value = dns_ip.to_string(); - if is_ipv6 { - ip_value = format!("[{ip_value}]") - } - netavark_hostport_dn_chain.create = true; - for proto in ["udp", "tcp"] { - netavark_hostport_dn_chain.build_rule(VarkRule { - rule: format!( - "-j {} -d {} -p {} --dport {} --to-destination {}:{}", - DNAT, dns_ip, proto, 53, ip_value, pfwd.dns_port - ), - // rule should be first otherwise another container might hijack all 53 traffic to itself - position: Some(1), - td_policy: Some(TeardownPolicy::OnComplete), - }); - } - } - } - - if let Some(ports) = pfwd.port_mappings { - for i in ports { - let host_ip = if i.host_ip.is_empty() { - None - } else { - match i.host_ip.parse() { - Ok(ip) => match ip { - IpAddr::V4(v4) => { - if is_ipv6 { - continue; - } - if !v4.is_unspecified() { - Some(IpAddr::V4(v4)) - } else { - None - } - } - IpAddr::V6(v6) => { - if !is_ipv6 { - continue; - } - if !v6.is_unspecified() { - Some(IpAddr::V6(v6)) - } else { - None - } - } - }, - Err(_) => { - return Err(NetavarkError::msg(format!( - "invalid host ip \"{}\" provided for port {}", - i.host_ip, i.host_port, - ))); - } - } - }; - - // hostport dnat - let is_range = i.range > 1; - let mut host_port = i.host_port.to_string(); - if is_range { - host_port = format!("{}:{}", i.host_port, (i.host_port + (i.range - 1))) - } - netavark_hostport_dn_chain.build_rule(VarkRule::new( - format!( - // I'm leaving this commented code for now in the case - // we need to revert. - // "-j {} -p {} -m multiport --destination-ports {} {}", - "-j {} -p {} --dport {} {}", - network_dn_chain_name, i.protocol, &host_port, comment_dn_network_cid - ), - None, - )); - - let mut dn_setmark_rule_localhost = format!( - "-j {} -s {} -p {} --dport {}", - NETAVARK_HOSTPORT_SETMARK, network_address, i.protocol, &host_port - ); - - let mut dn_setmark_rule_subnet = format!( - "-j {} -s {} -p {} --dport {}", - NETAVARK_HOSTPORT_SETMARK, localhost_ip, i.protocol, &host_port - ); - - // if a destination ip address is provided, we need to alter - // the rule a bit - if let Some(host_ip) = host_ip { - dn_setmark_rule_localhost = format!("{dn_setmark_rule_localhost} -d {host_ip}"); - dn_setmark_rule_subnet = format!("{dn_setmark_rule_subnet} -d {host_ip}"); - } - - // dn container (the actual port usages) - netavark_hashed_dn_chain.build_rule(VarkRule::new(dn_setmark_rule_localhost, None)); - - netavark_hashed_dn_chain.build_rule(VarkRule::new(dn_setmark_rule_subnet, None)); - - let mut container_ip_value = container_ip.to_string(); - if is_ipv6 { - container_ip_value = format!("[{container_ip_value}]") - } - let mut container_port = i.container_port.to_string(); - if is_range { - container_port = format!( - "{}-{}/{}", - i.container_port, - (i.container_port + (i.range - 1)), - i.host_port - ); - } - let mut dnat_rule = format!( - "-j {} -p {} --to-destination {}:{} --destination-port {}", - DNAT, i.protocol, container_ip_value, container_port, &host_port - ); - - // if a destination ip address is provided, we need to alter - // the rule a bit - if let Some(host_ip) = host_ip { - dnat_rule = format!("{dnat_rule} -d {host_ip}") - } - netavark_hashed_dn_chain.build_rule(VarkRule::new(dnat_rule, None)); - } - }; - - // The order is important here. Be certain before changing it - chains.push(netavark_hashed_dn_chain); - chains.push(netavark_hostport_dn_chain); - chains.push(prerouting_chain); - chains.push(output_chain); - - Ok(chains) -} diff --git a/src/network/bridge.rs b/src/network/bridge.rs index 36f554438..e0e680c84 100644 --- a/src/network/bridge.rs +++ b/src/network/bridge.rs @@ -12,7 +12,7 @@ use crate::{ error::{ErrorWrap, NetavarkError, NetavarkErrorList, NetavarkResult}, exec_netns, firewall::{ - iptables::MAX_HASH_SIZE, + nft::MAX_HASH_SIZE, state::{remove_fw_config, write_fw_config}, }, network::{constants, sysctl::disable_ipv6_autoconf, types}, diff --git a/src/network/types.rs b/src/network/types.rs index f3b2fcecd..fa8bfe88f 100644 --- a/src/network/types.rs +++ b/src/network/types.rs @@ -62,7 +62,7 @@ pub struct Network { /// NetworkOptions for a given container. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NetworkOptions { - /// The container id, used for iptables comments and ipam allocation. + /// The container id, used for ipam allocation. #[serde(rename = "container_id")] pub container_id: String, diff --git a/test/100-bridge-iptables.bats b/test/100-bridge-iptables.bats deleted file mode 100644 index 68b245e38..000000000 --- a/test/100-bridge-iptables.bats +++ /dev/null @@ -1,1104 +0,0 @@ -#!/usr/bin/env bats -*- bats -*- -# -# bridge driver tests with iptables firewall driver -# - -load helpers - -fw_driver=iptables - -@test "check iptables driver is in use" { - RUST_LOG=netavark=info run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path) - assert "${lines[0]}" "==" "[INFO netavark::firewall] Using iptables firewall driver" "iptables driver is in use" -} - -@test "$fw_driver - internal network" { - run_in_host_netns iptables -t nat -nvL - before="$output" - - run_netavark --file ${TESTSDIR}/testfiles/internal.json setup $(get_container_netns_path) - - run_in_host_netns iptables -t nat -nvL - assert "$output" == "$before" "make sure tables have not changed" - - run_in_container_netns ip route show - assert "$output" "!~" "default" "No default route for internal networks" - - run_in_container_netns ping -c 1 10.88.0.1 - - run_netavark --file ${TESTSDIR}/testfiles/internal.json teardown $(get_container_netns_path) -} - -@test "$fw_driver - simple bridge" { - run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path) - result="$output" - assert_json "$result" 'has("podman")' == "true" "object key exists" - - mac=$(jq -r '.podman.interfaces.eth0.mac_address' <<<"$result") - # check that interface exists - run_in_container_netns ip -j --details link show eth0 - link_info="$output" - assert_json "$link_info" ".[].address" == "$mac" "MAC matches container mac" - assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Container interface is up" - assert_json "$link_info" ".[].linkinfo.info_kind" == "veth" "Container interface is a veth device" - - ipaddr="10.88.0.2/16" - run_in_container_netns ip addr show eth0 - assert "$output" =~ "$ipaddr" "IP address matches container address" - assert_json "$result" ".podman.interfaces.eth0.subnets[0].ipnet" == "$ipaddr" "Result contains correct IP address" - - run_in_host_netns ip -j --details link show podman0 - link_info="$output" - assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Host bridge interface is up" - assert_json "$link_info" ".[].linkinfo.info_kind" == "bridge" "The bridge interface is actually a bridge" - bridge_mac=$(jq -r '.[].address' <<<"$link_info") - - run_in_host_netns ip -j link show veth0 - veth_info="$output" - assert_json "$veth_info" ".[].address" != "$bridge_mac" "Bridge and Veth must have different mac address" - - ipaddr="10.88.0.1" - run_in_host_netns ip addr show podman0 - assert "$output" =~ "$ipaddr" "IP address matches bridge gateway address" - assert_json "$result" ".podman.interfaces.eth0.subnets[0].gateway" == "$ipaddr" "Result contains gateway address" - - # check that the loopback adapter is up - run_in_container_netns ip addr show lo - assert "$output" =~ "127.0.0.1" "Loopback adapter is up (has address)" - - run_in_host_netns ping -c 1 10.88.0.2 - - check_simple_bridge_iptables - - run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json teardown $(get_container_netns_path) - - # now check that iptables rules are gone - run_in_host_netns iptables -S - - # check FORWARD rules - run_in_host_netns iptables -S FORWARD - assert "${lines[1]}" == "-A FORWARD -m comment --comment \"netavark firewall rules\" -j NETAVARK_FORWARD" "FORWARD rule" - assert "${#lines[@]}" = 2 "too many FORWARD rules after teardown" - - # rule 1 should be DROP for any existing networks - run_in_host_netns iptables -S NETAVARK_FORWARD - assert "${lines[1]}" == "-A NETAVARK_FORWARD -m conntrack --ctstate INVALID -j DROP" "NETAVARK_FORWARD rule 1" - assert "${#lines[@]}" = 2 "too many NETAVARK_FORWARD rules after teardown" - - # check POSTROUTING nat rules - run_in_host_netns iptables -S POSTROUTING -t nat - assert "${lines[1]}" =~ "-A POSTROUTING -j NETAVARK-HOSTPORT-MASQ" "POSTROUTING HOSTPORT-MASQ rule" - assert "${#lines[@]}" = 2 "too many POSTROUTING rules after teardown" - - # NETAVARK-1D8721804F16F chain should not exists - expected_rc=1 run_in_host_netns iptables -nvL NETAVARK-1D8721804F16F -t nat - - # bridge should be removed on teardown - expected_rc=1 run_in_host_netns ip addr show podman0 -} - -@test "$fw_driver - bridge with static routes" { - # add second interface and routes through that interface to test proper teardown - run_in_container_netns ip link add type dummy - run_in_container_netns ip a add 10.91.0.10/24 dev dummy0 - run_in_container_netns ip link set dummy0 up - - run_netavark --file ${TESTSDIR}/testfiles/bridge-staticroutes.json setup $(get_container_netns_path) - - # check static routes - run_in_container_netns ip r - assert "$output" "=~" "10.89.0.0/24 via 10.88.0.2" "static route not set" - assert "$output" "=~" "10.90.0.0/24 via 10.88.0.3" "static route not set" - assert "$output" "=~" "10.92.0.0/24 via 10.91.0.1" "static route not set" - - run_netavark --file ${TESTSDIR}/testfiles/bridge-staticroutes.json teardown $(get_container_netns_path) - - # check static routes get removed - assert "$output" "!~" "10.89.0.0/24 via 10.88.0.2" "static route not set" - assert "$output" "!~" "10.90.0.0/24 via 10.88.0.3" "static route not set" - assert "$output" "!~" "10.92.0.0/24 via 10.91.0.1" "static route not removed" -} - -@test "$fw_driver - bridge with no default route" { - run_netavark --file ${TESTSDIR}/testfiles/bridge-nodefaultroute.json setup $(get_container_netns_path) - - run_in_container_netns ip r - assert "$output" "!~" "default" "default route exists" - - run_in_container_netns ip -6 r - assert "$output" "!~" "default" "default route exists" - - run_netavark --file ${TESTSDIR}/testfiles/bridge-nodefaultroute.json teardown $(get_container_netns_path) - assert "" "no errors" -} - -@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)) - - NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ - 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" - - # Use run_helper instead of run_netavark here to check network namespace detection logic. - # See https://github.com/containers/netavark/issues/911 for details. - NETAVARK_DNS_PORT="$dns_port" run_helper $NETAVARK --config "$NETAVARK_TMPDIR/config" --rootless "$rootless" --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ - update podman1 --network-dns-servers 8.8.8.8 - assert "$output" = "" - - # after update the pid should never change - aardvark_pid2=$(cat "$NETAVARK_TMPDIR/config/aardvark-dns/aardvark.pid") - assert "$aardvark_pid2" == "$aardvark_pid" "aardvark-dns pid after nv update" - - # 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" - - # remove network and check running and verify if aardvark config has no nameserver - NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ - update podman1 --network-dns-servers "" - - # 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" "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" - -} - -# netavark must do no-op on upates when no aardvark config is there -@test "run netavark update - no-op" { - # get a random port directly to avoid low ports e.g. 53 would not create iptables - dns_port=$((RANDOM+10000)) - - NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ - update podman1 --network-dns-servers 8.8.8.8 -} - -@test "$fw_driver - ipv6 bridge" { - run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge.json setup $(get_container_netns_path) - result="$output" - assert_json "$result" 'has("podman1")' == "true" "object key exists" - - mac=$(jq -r '.podman1.interfaces.eth0.mac_address' <<<"$result") - # check that interface exists - run_in_container_netns ip -j --details link show eth0 - link_info="$output" - assert_json "$link_info" ".[].address" == "$mac" "MAC matches container mac" - assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Container interface is up" - assert_json "$link_info" ".[].linkinfo.info_kind" == "veth" "Container interface is a veth device" - - ipaddr="fd10:88:a::2/64" - run_in_container_netns ip addr show eth0 - assert "$output" =~ "$ipaddr" "IP address matches container address" - assert_json "$result" ".podman1.interfaces.eth0.subnets[0].ipnet" == "$ipaddr" "Result contains correct IP address" - - run_in_host_netns ip -j --details link show podman1 - link_info="$output" - assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Host bridge interface is up" - assert_json "$link_info" ".[].linkinfo.info_kind" == "bridge" "The bridge interface is actually a bridge" - - ipaddr="fd10:88:a::1" - run_in_host_netns ip addr show podman1 - assert "$output" =~ "$ipaddr" "IP address matches bridge gateway address" - assert_json "$result" ".podman1.interfaces.eth0.subnets[0].gateway" == "$ipaddr" "Result contains gateway address" - - # check that the loopback adapter is up - run_in_container_netns ip addr show lo - assert "$output" =~ "127.0.0.1" "Loopback adapter is up (has address)" - - run_in_host_netns ping6 -c 1 fd10:88:a::2 - - run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge.json teardown $(get_container_netns_path) -} - -@test "$fw_driver - ipv6 bridge with static routes" { - # add second interface and routes through that interface to test proper teardown - run_in_container_netns ip link add type dummy - run_in_container_netns ip a add fd10:49:b::2/64 dev dummy0 - run_in_container_netns ip link set dummy0 up - - run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge-staticroutes.json setup $(get_container_netns_path) - - # check static routes - run_in_container_netns ip -6 -br r - assert "$output" "=~" "fd10:89:b::/64 via fd10:88:a::ac02" "static route not set" - assert "$output" "=~" "fd10:89:c::/64 via fd10:88:a::ac03" "static route not set" - assert "$output" "=~" "fd10:51:b::/64 via fd10:49:b::30" "static route not set" - - run_netavark --file ${TESTSDIR}/testfiles/ipv6-bridge-staticroutes.json teardown $(get_container_netns_path) - - # check static routes get removed - run_in_container_netns ip -6 -br r - assert "$output" "!~" "fd10:89:b::/64 via fd10:88:a::ac02" "static route not removed" - assert "$output" "!~" "fd10:89:c::/64 via fd10:88:a::ac03" "static route not removed" - assert "$output" "!~" "fd10:51:b::/64 via fd10:49:b::30" "static route not removed" - - run_in_container_netns ip link delete dummy0 -} - -@test "$fw_driver - bridge driver must generate config for aardvark with custom dns server" { - run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-custom-dns-server.json \ - setup $(get_container_netns_path) - - run_in_host_netns iptables -S NETAVARK_INPUT - assert "${lines[1]}" == "-A NETAVARK_INPUT -s 10.89.3.0/24 -p udp -m udp --dport 53 -j ACCEPT" "ipv4 dns udp accept rule" - assert "${lines[2]}" == "-A NETAVARK_INPUT -s 10.89.3.0/24 -p tcp -m tcp --dport 53 -j ACCEPT" "ipv4 dns tcp accept rule" - run_in_host_netns ip6tables -S NETAVARK_INPUT - assert "${lines[1]}" == "-A NETAVARK_INPUT -s fd10:88:a::/64 -p udp -m udp --dport 53 -j ACCEPT" "ipv6 dns udp accept rule" - assert "${lines[2]}" == "-A NETAVARK_INPUT -s fd10:88:a::/64 -p tcp -m tcp --dport 53 -j ACCEPT" "ipv6 dns tcp accept rule" - - # 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" "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$" "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 53 run" "aardvark not running or bad options" -} - -@test "$fw_driver - bridge driver must generate config for aardvark with multiple custom dns server" { - # get a random port directly to avoid low ports e.g. 53 would not create iptables - dns_port=$((RANDOM+10000)) - - NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-multiple-custom-dns-server.json \ - 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" "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" -} - -@test "$fw_driver - bridge driver must generate config for aardvark with multiple custom dns server with network dns servers" { - # get a random port directly to avoid low ports e.g. 53 would not create iptables - dns_port=$((RANDOM+10000)) - - NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ - 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" -} - -@test "$fw_driver - dual stack dns with alt port" { - # get a random port directly to avoid low ports e.g. 53 would not create iptables - dns_port=$((RANDOM+10000)) - - NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ - setup $(get_container_netns_path) - - # check iptables - run_in_host_netns iptables -t nat -S NETAVARK-HOSTPORT-DNAT - assert "${lines[2]}" == "-A NETAVARK-HOSTPORT-DNAT -d 10.89.3.1/32 -p udp -m udp --dport 53 -j DNAT --to-destination 10.89.3.1:$dns_port" "ipv4 dns forward rule" - assert "${lines[1]}" == "-A NETAVARK-HOSTPORT-DNAT -d 10.89.3.1/32 -p tcp -m tcp --dport 53 -j DNAT --to-destination 10.89.3.1:$dns_port" "ipv4 dns tcp forward rule" - run_in_host_netns ip6tables -t nat -S NETAVARK-HOSTPORT-DNAT - assert "${lines[2]}" == "-A NETAVARK-HOSTPORT-DNAT -d fd10:88:a::1/128 -p udp -m udp --dport 53 -j DNAT --to-destination [fd10:88:a::1]:$dns_port" "ipv6 dns forward rule" - assert "${lines[1]}" == "-A NETAVARK-HOSTPORT-DNAT -d fd10:88:a::1/128 -p tcp -m tcp --dport 53 -j DNAT --to-destination [fd10:88:a::1]:$dns_port" "ipv6 dns tcp forward rule" - - # 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" "aardvark set to listen to all IPs" - assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename$" "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" - - # test redirection actually works - run_in_container_netns dig +short "somename.dns.podman" @10.89.3.1 A "somename.dns.podman" @10.89.3.1 AAAA - assert "${lines[0]}" =~ "10.89.3.2" "ipv4 dns resolution works 1/2" - assert "${lines[1]}" =~ "fd10:88:a::2" "ipv6 dns resolution works 2/2" - - run_in_container_netns dig +short "somename.dns.podman" @fd10:88:a::1 - assert "${lines[0]}" =~ "10.89.3.2" "ipv6 dns resolution works" - - NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ - teardown $(get_container_netns_path) - - # check iptables got removed - run_in_host_netns iptables -t nat -S NETAVARK-HOSTPORT-DNAT - assert "${#lines[@]}" = 1 "too many v4 NETAVARK_HOSTPORT-DNAT rules after teardown" - run_in_host_netns ip6tables -t nat -S NETAVARK-HOSTPORT-DNAT - assert "${#lines[@]}" = 1 "too many v6 NETAVARK_HOSTPORT-DNAT rules after teardown" - - # check aardvark config got cleared, process killed - expected_rc=2 run_helper ls "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" - expected_rc=1 run_helper ps "$aardvark_pid" -} - -@test "$fw_driver - dns with default drop policy" { - run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ - setup $(get_container_netns_path) - - run_in_host_netns iptables -P INPUT DROP - run_in_host_netns iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT - - # 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" "aardvark set to listen to all IPs" - assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename$" "aardvark config's container" - assert "${#lines[@]}" = 2 "too many lines in aardvark config" - - # test redirection actually works - run_in_container_netns dig +short "somename.dns.podman" @10.89.3.1 A "somename.dns.podman" @10.89.3.1 AAAA - assert "${lines[0]}" =~ "10.89.3.2" "ipv4 dns resolution works 1/2" - assert "${lines[1]}" =~ "fd10:88:a::2" "ipv6 dns resolution works 2/2" - - run_in_container_netns dig +short "somename.dns.podman" @fd10:88:a::1 - assert "${lines[0]}" =~ "10.89.3.2" "ipv6 dns resolution works" - - run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ - teardown $(get_container_netns_path) - - # check iptables got removed - run_in_host_netns iptables -t nat -S NETAVARK-HOSTPORT-DNAT - assert "${#lines[@]}" = 1 "too many v4 NETAVARK_HOSTPORT-DNAT rules after teardown" - run_in_host_netns ip6tables -t nat -S NETAVARK-HOSTPORT-DNAT - assert "${#lines[@]}" = 1 "too many v6 NETAVARK_HOSTPORT-DNAT rules after teardown" - - # check aardvark config got cleared, process killed - expected_rc=2 run_helper ls "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" - expected_rc=1 run_helper ps "$aardvark_pid" -} - -@test "$fw_driver - dns with default drop policy with non-default dns port" { - # get a random port - dns_port=$((RANDOM+10000)) - - NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ - setup $(get_container_netns_path) - - # check iptables - run_in_host_netns iptables -t filter -S NETAVARK_INPUT - assert "${lines[1]}" == "-A NETAVARK_INPUT -s 10.89.3.0/24 -p udp -m udp --dport $dns_port -j ACCEPT" "ipv4 dns forward rule" - run_in_host_netns ip6tables -t filter -S NETAVARK_INPUT - assert "${lines[1]}" == "-A NETAVARK_INPUT -s fd10:88:a::/64 -p udp -m udp --dport $dns_port -j ACCEPT" "ipv6 dns forward rule" - - - run_in_host_netns iptables -P INPUT DROP - run_in_host_netns iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT - - # 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" "aardvark set to listen to all IPs" - assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename$" "aardvark config's container" - assert "${#lines[@]}" = 2 "too many lines in aardvark config" - - # test redirection actually works - run_in_container_netns dig +short "somename.dns.podman" @10.89.3.1 A "somename.dns.podman" @10.89.3.1 AAAA - assert "${lines[0]}" =~ "10.89.3.2" "ipv4 dns resolution works 1/2" - assert "${lines[1]}" =~ "fd10:88:a::2" "ipv6 dns resolution works 2/2" - - run_in_container_netns dig +short "somename.dns.podman" @fd10:88:a::1 - assert "${lines[0]}" =~ "10.89.3.2" "ipv6 dns resolution works" - - NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ - teardown $(get_container_netns_path) - - # check iptables got removed - run_in_host_netns iptables -t nat -S NETAVARK-HOSTPORT-DNAT - assert "${#lines[@]}" = 1 "too many v4 NETAVARK_HOSTPORT-DNAT rules after teardown" - run_in_host_netns ip6tables -t nat -S NETAVARK-HOSTPORT-DNAT - assert "${#lines[@]}" = 1 "too many v6 NETAVARK_HOSTPORT-DNAT rules after teardown" - - # check aardvark config got cleared, process killed - expected_rc=2 run_helper ls "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" - expected_rc=1 run_helper ps "$aardvark_pid" -} - -@test "$fw_driver - check error message from netns thread" { - # create interface in netns to force error - run_in_container_netns ip link add eth0 type dummy - - expected_rc=1 run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path) - assert_json ".error" "create veth pair: interface eth0 already exists on container namespace: Netlink error: File exists (os error 17)" "interface exists on netns" -} - -@test "$fw_driver - port forwarding ipv4 - tcp" { - test_port_fw -} - -@test "$fw_driver - port forwarding ipv6 - tcp" { - test_port_fw ip=6 -} - -@test "$fw_driver - port forwarding dualstack - tcp" { - test_port_fw ip=dual -} - -@test "$fw_driver - port forwarding ipv4 - udp" { - test_port_fw proto=udp -} - -@test "$fw_driver - port forwarding ipv6 - udp" { - test_port_fw ip=6 proto=udp -} - -@test "$fw_driver - port forwarding dualstack - udp" { - test_port_fw ip=dual proto=udp -} - -@test "$fw_driver - port forwarding ipv4 - sctp" { - setup_sctp_kernel_module - test_port_fw proto=sctp -} - -@test "$fw_driver - port forwarding ipv6 - sctp" { - setup_sctp_kernel_module - test_port_fw ip=6 proto=sctp -} - -@test "$fw_driver - port forwarding dualstack - sctp" { - setup_sctp_kernel_module - test_port_fw ip=dual proto=sctp -} - -@test "$fw_driver - port range forwarding ipv4 - tcp" { - test_port_fw range=3 -} - -@test "$fw_driver - port range forwarding ipv6 - tcp" { - test_port_fw ip=6 range=3 -} - -@test "$fw_driver - port range forwarding ipv4 - udp" { - test_port_fw proto=udp range=3 -} - -@test "$fw_driver - port range forwarding ipv6 - udp" { - test_port_fw ip=6 proto=udp range=3 -} - -@test "$fw_driver - port range forwarding dual - udp" { - test_port_fw ip=dual proto=udp range=3 -} - -@test "$fw_driver - port range forwarding dual - tcp" { - test_port_fw ip=dual proto=tcp range=3 -} - - -@test "$fw_driver - port forwarding with hostip ipv4 - tcp" { - add_dummy_interface_on_host dummy0 "172.16.0.1/24" - test_port_fw hostip="172.16.0.1" -} - -@test "$fw_driver - port forwarding with hostip ipv4 dual stack - tcp" { - add_dummy_interface_on_host dummy0 "172.16.0.1/24" - test_port_fw ip=dual hostip="172.16.0.1" -} - -@test "$fw_driver - port forwarding with hostip ipv6 - tcp" { - add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" - test_port_fw ip=6 hostip="fd65:8371:648b:0c06::1" -} - -@test "$fw_driver - port forwarding with hostip ipv6 dual stack - tcp" { - add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" - test_port_fw ip=dual hostip="fd65:8371:648b:0c06::1" -} - -@test "$fw_driver - port forwarding with wildcard hostip ipv4 - tcp" { - add_dummy_interface_on_host dummy0 "172.16.0.1/24" - test_port_fw hostip="0.0.0.0" connectip="172.16.0.1" -} - -@test "$fw_driver - port forwarding with wildcard hostip ipv4 dual stack - tcp" { - add_dummy_interface_on_host dummy0 "172.16.0.1/24" - test_port_fw ip=dual hostip="0.0.0.0" connectip="172.16.0.1" -} - -@test "$fw_driver - port forwarding with wildcard hostip ipv6 - tcp" { - add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" - test_port_fw ip=6 hostip="::" connectip="fd65:8371:648b:0c06::1" -} - -@test "$fw_driver - port forwarding with wildcard hostip ipv6 dual stack - tcp" { - add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" - test_port_fw ip=dual hostip="::" connectip="fd65:8371:648b:0c06::1" -} - -@test "$fw_driver - port forwarding with hostip ipv4 - udp" { - add_dummy_interface_on_host dummy0 "172.16.0.1/24" - test_port_fw proto=udp hostip="172.16.0.1" -} - -@test "$fw_driver - port forwarding with hostip ipv6 - udp" { - add_dummy_interface_on_host dummy0 "fd65:8371:648b:0c06::1/64" - test_port_fw ip=6 proto=udp hostip="fd65:8371:648b:0c06::1" -} - -@test "$fw_driver - port forwarding with localhost - tcp" { - test_port_fw hostip="127.0.0.1" -} - -# Test that port forwarding works with strict Reverse Path Forwarding enabled on the host -@test "$fw_driver - port forwarding with two networks and RPF - tcp" { - # First, enable strict RPF on host/container ns. - run_in_host_netns sysctl -w net.ipv4.conf.all.rp_filter=1 - run_in_host_netns sysctl -w net.ipv4.conf.default.rp_filter=1 - run_in_container_netns sysctl -w net.ipv4.conf.all.rp_filter=1 - run_in_container_netns sysctl -w net.ipv4.conf.default.rp_filter=1 - - # We need a dummy interface with a host ip, - # if we connect directly to the bridge ip it doesn't reproduce. - add_dummy_interface_on_host dummy0 "10.0.0.1/24" - - run_netavark --file ${TESTSDIR}/testfiles/two-networks.json setup $(get_container_netns_path) - result="$output" - - run_in_host_netns cat /proc/sys/net/ipv4/conf/podman2/rp_filter - assert "2" "rp_filter podman2 bridge" - run_in_host_netns cat /proc/sys/net/ipv4/conf/podman3/rp_filter - assert "2" "rp_filter podman3 bridge" - - run_in_container_netns cat /proc/sys/net/ipv4/conf/eth0/rp_filter - assert "2" "rp_filter eth0 interface" - run_in_container_netns cat /proc/sys/net/ipv4/conf/eth1/rp_filter - assert "2" "rp_filter eth1 interface" - - # Important: Use the "host" ip here and not localhost or bridge ip. - run_connection_test "0" "tcp" 8080 "10.0.0.1" 8080 -} - -@test "bridge ipam none" { - read -r -d '\0' config < /proc/sys/net/ipv4/ip_forward" - run_in_container_netns sh -c "echo 1 > /proc/sys/net/ipv4/conf/default/arp_notify" - run_in_host_netns sh -c "echo 2 > /proc/sys/net/ipv4/conf/default/rp_filter" - run_in_host_netns sh -c "echo 1 > /proc/sys/net/ipv4/conf/default/route_localnet" - run_in_container_netns sh -c "echo 2 > /proc/sys/net/ipv4/conf/default/rp_filter" - run_in_host_netns mount -t proc -o ro,nosuid,nodev,noexec proc /proc - - run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path) - run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json teardown $(get_container_netns_path) - - run_in_host_netns mount -t proc -o remount,rw /proc - run_in_host_netns sh -c "echo 0 > /proc/sys/net/ipv4/ip_forward" - run_in_host_netns mount -t proc -o remount,ro /proc - - expected_rc=1 run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path) - assert_json ".error" "set sysctl net/ipv4/ip_forward: IO error: Read-only file system (os error 30)" "Sysctl error because fs is read only" -} - - -@test "$fw_driver - bridge static mac" { - mac="aa:bb:cc:dd:ee:ff" - - read -r -d '\0' config < Date: Mon, 10 Nov 2025 18:23:01 +0100 Subject: [PATCH 2/3] Add test cases that existed for iptables but not for nftables Signed-off-by: Mario Loriedo --- test/250-bridge-nftables.bats | 130 +++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 3 deletions(-) diff --git a/test/250-bridge-nftables.bats b/test/250-bridge-nftables.bats index ba0ff8b68..4ae9b5f06 100644 --- a/test/250-bridge-nftables.bats +++ b/test/250-bridge-nftables.bats @@ -147,8 +147,15 @@ export NETAVARK_FW=nftables 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 \ + # Use run_helper instead of run_netavark here to check network namespace detection logic. + # See https://github.com/containers/netavark/issues/911 for details. + NETAVARK_DNS_PORT="$dns_port" run_helper $NETAVARK --config "$NETAVARK_TMPDIR/config" --rootless "$rootless" --file ${TESTSDIR}/testfiles/dualstack-bridge-network-container-dns-server.json \ update podman1 --network-dns-servers 8.8.8.8 + assert "$output" = "" + + # after update the pid should never change + aardvark_pid2=$(cat "$NETAVARK_TMPDIR/config/aardvark-dns/aardvark.pid") + assert "$aardvark_pid2" == "$aardvark_pid" "aardvark-dns pid after nv update" # check aardvark config and running run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" @@ -271,7 +278,7 @@ export NETAVARK_FW=nftables } @test "$fw_driver - bridge driver must generate config for aardvark with multiple custom dns server" { - # get a random port directly to avoid low ports e.g. 53 would not create nftables + # get a random port directly to avoid low ports e.g. 53 would not create nftables rules dns_port=$((RANDOM+10000)) NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge-multiple-custom-dns-server.json \ @@ -351,6 +358,94 @@ export NETAVARK_FW=nftables expected_rc=1 run_helper ps "$aardvark_pid" } +@test "$fw_driver - dns with default drop policy" { + run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ + setup $(get_container_netns_path) + + run_in_host_netns nft add chain inet netavark INPUT \{ type filter hook input priority 0 \; policy drop \; \} + run_in_host_netns nft add rule inet netavark INPUT ct state related,established accept + run_in_host_netns nft add rule inet netavark INPUT meta l4proto ipv6-icmp accept # allow ICMPv6, required for DNS resolution + + # 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" "aardvark set to listen to all IPs" + assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename$" "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 53 run" "aardvark not running or bad options" + + # test redirection actually works + run_in_container_netns dig +short "somename.dns.podman" @10.89.3.1 A "somename.dns.podman" @10.89.3.1 AAAA + assert "${lines[0]}" =~ "10.89.3.2" "ipv4 dns resolution works 1/2" + assert "${lines[1]}" =~ "fd10:88:a::2" "ipv6 dns resolution works 2/2" + + run_in_container_netns dig +short "somename.dns.podman" @fd10:88:a::1 + assert "${lines[0]}" =~ "10.89.3.2" "ipv6 dns resolution works" + + run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ + teardown $(get_container_netns_path) + + # check nftables rules were removed + run_in_host_netns nft list chain inet netavark NETAVARK-HOSTPORT-DNAT + assert "${#lines[@]}" = 4 "too many v4 NETAVARK_HOSTPORT-DNAT rules after teardown" + + # check aardvark config got cleared, process killed + expected_rc=2 run_helper ls "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" + expected_rc=1 run_helper ps "$aardvark_pid" +} + +@test "$fw_driver - dns with default drop policy with non-default dns port" { + # get a random port + dns_port=$((RANDOM+10000)) + + NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ + setup $(get_container_netns_path) + + # check that random DNS port was added to nftables rules + run_in_host_netns nft list chain inet netavark NETAVARK-HOSTPORT-DNAT + assert "${lines[2]}" =~ "ip6 daddr fd10:88:a::1 meta l4proto \{ tcp, udp \} th dport 53 dnat ip6 to \[fd10:88:a::1\]:$dns_port" "DNS forward rule ip6" + assert "${lines[3]}" =~ "ip daddr 10.89.3.1 meta l4proto \{ tcp, udp \} th dport 53 dnat ip to 10.89.3.1:$dns_port" "DNS forward rule ip4" + + run_in_host_netns nft add chain inet netavark INPUT \{ type filter hook input priority 0 \; policy drop \; \} + run_in_host_netns nft add rule inet netavark INPUT ip saddr 10.89.3.0/24 meta l4proto \{ tcp, udp \} th dport $dns_port accept + run_in_host_netns nft add rule inet netavark INPUT ip6 saddr fd10:88:a::/64 meta l4proto \{ tcp, udp \} th dport $dns_port accept + run_in_host_netns nft add rule inet netavark INPUT ct state related,established accept + run_in_host_netns nft add rule inet netavark INPUT meta l4proto ipv6-icmp accept # allow ICMPv6, required for DNS resolution + + # 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" "aardvark set to listen to all IPs" + assert "${lines[1]}" =~ "^[0-9a-f]{64} 10.89.3.2 fd10:88:a::2 somename$" "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" + + # test redirection actually works + run_in_container_netns dig +short "somename.dns.podman" @10.89.3.1 A "somename.dns.podman" @10.89.3.1 AAAA + assert "${lines[0]}" =~ "10.89.3.2" "ipv4 dns resolution works 1/2" + assert "${lines[1]}" =~ "fd10:88:a::2" "ipv6 dns resolution works 2/2" + + run_in_container_netns dig +short "somename.dns.podman" @fd10:88:a::1 + assert "${lines[0]}" =~ "10.89.3.2" "ipv6 dns resolution works" + + NETAVARK_DNS_PORT="$dns_port" run_netavark --file ${TESTSDIR}/testfiles/dualstack-bridge.json \ + teardown $(get_container_netns_path) + + # check nftables rules were removed + run_in_host_netns nft list chain inet netavark NETAVARK-HOSTPORT-DNAT + assert "${#lines[@]}" = 4 "too many v4 NETAVARK_HOSTPORT-DNAT rules after teardown" + + # check aardvark config got cleared, process killed + expected_rc=2 run_helper ls "$NETAVARK_TMPDIR/config/aardvark-dns/podman1" + expected_rc=1 run_helper ps "$aardvark_pid" +} + @test "$fw_driver - check error message from netns thread" { # create interface in netns to force error run_in_container_netns ip link add eth0 type dummy @@ -477,6 +572,35 @@ export NETAVARK_FW=nftables test_port_fw hostip="127.0.0.1" } +# Test that port forwarding works with strict Reverse Path Forwarding enabled on the host +@test "$fw_driver - port forwarding with two networks and RPF - tcp" { + # First, enable strict RPF on host/container ns. + run_in_host_netns sysctl -w net.ipv4.conf.all.rp_filter=1 + run_in_host_netns sysctl -w net.ipv4.conf.default.rp_filter=1 + run_in_container_netns sysctl -w net.ipv4.conf.all.rp_filter=1 + run_in_container_netns sysctl -w net.ipv4.conf.default.rp_filter=1 + + # We need a dummy interface with a host ip, + # if we connect directly to the bridge ip it doesn't reproduce. + add_dummy_interface_on_host dummy0 "10.0.0.1/24" + + run_netavark --file ${TESTSDIR}/testfiles/two-networks.json setup $(get_container_netns_path) + result="$output" + + run_in_host_netns cat /proc/sys/net/ipv4/conf/podman2/rp_filter + assert "2" "rp_filter podman2 bridge" + run_in_host_netns cat /proc/sys/net/ipv4/conf/podman3/rp_filter + assert "2" "rp_filter podman3 bridge" + + run_in_container_netns cat /proc/sys/net/ipv4/conf/eth0/rp_filter + assert "2" "rp_filter eth0 interface" + run_in_container_netns cat /proc/sys/net/ipv4/conf/eth1/rp_filter + assert "2" "rp_filter eth1 interface" + + # Important: Use the "host" ip here and not localhost or bridge ip. + run_connection_test "0" "tcp" 8080 "10.0.0.1" 8080 +} + @test "bridge ipam none" { read -r -d '\0' config < Date: Wed, 5 Nov 2025 16:04:36 +0100 Subject: [PATCH 3/3] Minor documentation and style fixes Signed-off-by: Mario Loriedo --- docs/netavark-firewalld.7.md | 2 +- test-dhcp/README.md | 5 +++-- test/200-bridge-firewalld.bats | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/netavark-firewalld.7.md b/docs/netavark-firewalld.7.md index 43277ffa6..e73010e14 100644 --- a/docs/netavark-firewalld.7.md +++ b/docs/netavark-firewalld.7.md @@ -18,7 +18,7 @@ There is also a dedicated firewalld driver in Netavark. This driver uses the firewalld DBus API to natively interact with firewalld. It can be enabled by setting `firewall_driver` to `firewalld` in `containers.conf`. The native firewalld driver offers better integration with firewalld, but presently suffers from several limitations. -It does not support isolation (the `--opt isolate=` option to `podman network create`. +It does not support isolation (the `--opt isolate=` option to `podman network create`). Connections to ports forwarded by a container on the same host can only be made through the IPv4 localhost IP (`127.0.0.1`). Using other IPs on the host will not work, unless the connection comes from a separate host. diff --git a/test-dhcp/README.md b/test-dhcp/README.md index 5213d3bf4..b2c331896 100644 --- a/test-dhcp/README.md +++ b/test-dhcp/README.md @@ -3,9 +3,10 @@ ## Running tests To run the tests locally in your sandbox, you can use one of these methods: -* bats ./test/001-basic.bats # runs just the specified test -* bats ./test/ # runs all +* bats ./test-dhcp/001-basic.bats # runs just the specified test +* bats ./test-dhcp/ # runs all ## Requirements - bats - jq +- dnsmasq diff --git a/test/200-bridge-firewalld.bats b/test/200-bridge-firewalld.bats index ce6000c57..45c1e2be2 100644 --- a/test/200-bridge-firewalld.bats +++ b/test/200-bridge-firewalld.bats @@ -386,7 +386,7 @@ function setup() { function strict_port_forwarding_enabled_should_deny_port_forwarding() { local fw_driver="$1" if [ -z "$fw_driver" ]; then - echo "Error: No fw_driver provided." >&2 + echo "Error: No fw_driver provided." >&2 return 1 fi @@ -402,7 +402,7 @@ function strict_port_forwarding_enabled_should_deny_port_forwarding() { function strict_port_forwarding_disabled_should_allow_port_forwarding() { local fw_driver="$1" if [ -z "$fw_driver" ]; then - echo "Error: No fw_driver provided." >&2 + echo "Error: No fw_driver provided." >&2 return 1 fi @@ -421,7 +421,7 @@ function strict_port_forwarding_disabled_should_allow_port_forwarding() { function strict_port_forwarding_invalid_value_should_warn_and_allow_port_forwarding() { local fw_driver="$1" if [ -z "$fw_driver" ]; then - echo "Error: No fw_driver provided." >&2 + echo "Error: No fw_driver provided." >&2 return 1 fi