Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/firewall/firewalld.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,15 @@ impl firewall::FirewallDriver for FirewallD {
))
}
};
// Determine if masquerade should be enabled based on SNAT configuration
// For firewalld, we check if either IPv4 or IPv6 SNAT is enabled
// since firewalld policy applies to both address families at the same level.
// Note: Unlike nftables/iptables, firewalld applies masquerade at the policy level,
// not per-subnet, so we use OR logic - if any protocol needs SNAT, enable it.
let enable_masquerade = network_setup.snat_ipv4 || network_setup.snat_ipv6;

need_reload |= match add_policy_if_not_exist(
&self.conn, POLICYNAME, ZONENAME, "ANY", "ACCEPT", true, None,
&self.conn, POLICYNAME, ZONENAME, "ANY", "ACCEPT", enable_masquerade, None,
) {
Ok(b) => b,
Err(e) => {
Expand Down
14 changes: 14 additions & 0 deletions src/firewall/iptables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ impl firewall::FirewallDriver for IptablesDriver {
conn = &self.conn6;
}

let enable_snat = if is_ipv6 {
network_setup.snat_ipv6
} else {
network_setup.snat_ipv4
};

let chains = get_network_chains(
conn,
network,
Expand All @@ -62,6 +68,7 @@ impl firewall::FirewallDriver for IptablesDriver {
network_setup.bridge_name.clone(),
network_setup.isolation,
network_setup.dns_port,
enable_snat,
);

create_network_chains(chains)?;
Expand All @@ -83,6 +90,12 @@ impl firewall::FirewallDriver for IptablesDriver {
if is_ipv6 {
conn = &self.conn6;
}
let enable_snat = if is_ipv6 {
tear.config.snat_ipv6
} else {
tear.config.snat_ipv4
};

let chains = get_network_chains(
conn,
network,
Expand All @@ -91,6 +104,7 @@ impl firewall::FirewallDriver for IptablesDriver {
tear.config.bridge_name.clone(),
tear.config.isolation,
tear.config.dns_port,
enable_snat,
);

for c in &chains {
Expand Down
28 changes: 18 additions & 10 deletions src/firewall/nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,17 +379,25 @@ impl firewall::FirewallDriver for Nftables {
));

// Subnet chain: ip daddr != 224.0.0.0/4 masquerade
let multicast_address: IpNet = match subnet {
IpNet::V4(_) => "224.0.0.0/4".parse()?,
IpNet::V6(_) => "ff::00/8".parse()?,
// Only add masquerade rule if SNAT is enabled for this address family
let should_add_masquerade = match subnet {
IpNet::V4(_) => network_setup.snat_ipv4,
IpNet::V6(_) => network_setup.snat_ipv6,
};
batch.add(make_rule(
chain.clone(),
Cow::Owned(vec![
get_subnet_match(&multicast_address, "daddr", stmt::Operator::NEQ),
stmt::Statement::Masquerade(None),
]),
));

if should_add_masquerade {
let multicast_address: IpNet = match subnet {
IpNet::V4(_) => "224.0.0.0/4".parse()?,
IpNet::V6(_) => "ff::00/8".parse()?,
};
batch.add(make_rule(
chain.clone(),
Cow::Owned(vec![
get_subnet_match(&multicast_address, "daddr", stmt::Operator::NEQ),
stmt::Statement::Masquerade(None),
]),
));
}

// Next, populate basic chains with forwarding rules
// Input chain: ip saddr <subnet> udp dport 53 accept
Expand Down
4 changes: 3 additions & 1 deletion src/firewall/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,10 @@ mod tests {
network_hash_name: "hash".to_string(),
isolation: IsolateOption::Never,
dns_port: 53,
snat_ipv4: true,
snat_ipv6: true,
};
let net_conf_json = r#"{"subnets":["10.0.0.0/24"],"bridge_name":"bridge","network_id":"c2c8a073252874648259997d53b0a1bffa491e21f04bc1bf8609266359931395","network_hash_name":"hash","isolation":"Never","dns_port":53}"#;
let net_conf_json = r#"{"subnets":["10.0.0.0/24"],"bridge_name":"bridge","network_id":"c2c8a073252874648259997d53b0a1bffa491e21f04bc1bf8609266359931395","network_hash_name":"hash","isolation":"Never","dns_port":53,"snat_ipv4":true,"snat_ipv6":true}"#;

let port_conf = PortForwardConfig {
container_id: container_id.to_string(),
Expand Down
18 changes: 11 additions & 7 deletions src/firewall/varktables/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ pub fn get_network_chains<'a>(
interface_name: String,
isolation: IsolateOption,
dns_port: u16,
enable_snat: bool,
) -> Vec<VarkChain<'a>> {
let mut chains = Vec::new();
let prefixed_network_hash_name = format!("{}-{}", "NETAVARK", network_hash_name);
Expand All @@ -218,14 +219,17 @@ pub fn get_network_chains<'a>(
Some(TeardownPolicy::OnComplete),
));

let mut multicast_dest = MULTICAST_NET_V4;
if is_ipv6 {
multicast_dest = MULTICAST_NET_V6;
// Only add MASQUERADE rule if SNAT is enabled for this address family
if enable_snat {
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),
));
}
hashed_network_chain.build_rule(VarkRule::new(
format!("! -d {multicast_dest} -j {MASQUERADE}"),
Some(TeardownPolicy::OnComplete),
));
chains.push(hashed_network_chain);

// POSTROUTING
Expand Down
29 changes: 29 additions & 0 deletions src/network/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,33 @@ impl<'a> Bridge<'a> {
) -> NetavarkResult<(SetupNetwork, PortForwardConfig<'a>)> {
let id_network_hash =
CoreUtils::create_network_hash(&self.info.network.name, MAX_HASH_SIZE);
// Parse SNAT configuration from network options
// Format: "snat_ipv4": "true|false", "snat_ipv6": "true|false"
// Defaults to true for backward compatibility
let snat_ipv4 = match parse_option::<bool>(
&self.info.network.options,
"snat_ipv4",
) {
Ok(Some(val)) => val,
Ok(None) => true, // Default to true
Err(e) => {
debug!("Failed to parse snat_ipv4 option: {}, using default (true)", e);
true
}
};

let snat_ipv6 = match parse_option::<bool>(
&self.info.network.options,
"snat_ipv6",
) {
Ok(Some(val)) => val,
Ok(None) => true, // Default to true
Err(e) => {
debug!("Failed to parse snat_ipv6 option: {}, using default (true)", e);
true
}
};

let sn = SetupNetwork {
subnets: self
.info
Expand All @@ -446,6 +473,8 @@ impl<'a> Bridge<'a> {
network_hash_name: id_network_hash.clone(),
isolation: isolate,
dns_port: self.info.dns_port,
snat_ipv4,
snat_ipv6,
};

let mut has_ipv4 = false;
Expand Down
14 changes: 14 additions & 0 deletions src/network/internal_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ pub struct SetupNetwork {
pub isolation: IsolateOption,
/// port used for the dns server
pub dns_port: u16,
/// enable SNAT for IPv4 traffic (default: true)
#[serde(default = "default_snat_ipv4")]
pub snat_ipv4: bool,
/// enable SNAT for IPv6 traffic (default: true)
#[serde(default = "default_snat_ipv6")]
pub snat_ipv6: bool,
}

fn default_snat_ipv4() -> bool {
true
}

fn default_snat_ipv6() -> bool {
true
}

#[derive(Debug)]
Expand Down