Skip to content

Commit fee02a5

Browse files
committed
Add outbound_addr to allow for SNAT instead of MASQ
Signed-off-by: lto-dev <[email protected]>
1 parent c4318b5 commit fee02a5

File tree

8 files changed

+82
-13
lines changed

8 files changed

+82
-13
lines changed

src/firewall/iptables.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ impl firewall::FirewallDriver for IptablesDriver {
6262
network_setup.bridge_name.clone(),
6363
network_setup.isolation,
6464
network_setup.dns_port,
65+
network_setup.outbound_addr,
6566
);
6667

6768
create_network_chains(chains)?;
@@ -91,6 +92,7 @@ impl firewall::FirewallDriver for IptablesDriver {
9192
tear.config.bridge_name.clone(),
9293
tear.config.isolation,
9394
tear.config.dns_port,
95+
tear.config.outbound_addr,
9496
);
9597

9698
for c in &chains {

src/firewall/nft.rs

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -367,18 +367,58 @@ impl firewall::FirewallDriver for Nftables {
367367
],
368368
));
369369

370-
// Subnet chain: ip daddr != 224.0.0.0/4 masquerade
370+
// Subnet chain: ip daddr != 224.0.0.0/4 snat/masquerade
371371
let multicast_address: IpNet = match subnet {
372372
IpNet::V4(_) => "224.0.0.0/4".parse()?,
373373
IpNet::V6(_) => "ff::00/8".parse()?,
374374
};
375-
batch.add(make_rule(
376-
&chain,
377-
vec![
378-
get_subnet_match(&multicast_address, "daddr", stmt::Operator::NEQ),
379-
stmt::Statement::Masquerade(None),
380-
],
381-
));
375+
376+
// If outbound_addr is set and valid IPv4, use SNAT, otherwise use MASQUERADE
377+
if let Some(addr) = network_setup.outbound_addr {
378+
if let IpNet::V4(_) = subnet {
379+
if addr.is_ipv4() {
380+
log::trace!("Creating SNAT rule with outbound address {}", addr);
381+
batch.add(make_rule(
382+
&chain,
383+
vec![
384+
get_subnet_match(&multicast_address, "daddr", stmt::Operator::NEQ),
385+
stmt::Statement::SNAT(Some(stmt::NAT {
386+
addr: Some(expr::Expression::String(addr.to_string())),
387+
family: Some(stmt::NATFamily::IP),
388+
port: None,
389+
flags: None,
390+
})),
391+
],
392+
));
393+
} else {
394+
log::trace!("Outbound address {} is not IPv4, using default MASQUERADE rule", addr);
395+
batch.add(make_rule(
396+
&chain,
397+
vec![
398+
get_subnet_match(&multicast_address, "daddr", stmt::Operator::NEQ),
399+
stmt::Statement::Masquerade(None),
400+
],
401+
));
402+
}
403+
} else {
404+
batch.add(make_rule(
405+
&chain,
406+
vec![
407+
get_subnet_match(&multicast_address, "daddr", stmt::Operator::NEQ),
408+
stmt::Statement::Masquerade(None),
409+
],
410+
));
411+
}
412+
} else {
413+
log::trace!("No outbound address set, using default MASQUERADE rule");
414+
batch.add(make_rule(
415+
&chain,
416+
vec![
417+
get_subnet_match(&multicast_address, "daddr", stmt::Operator::NEQ),
418+
stmt::Statement::Masquerade(None),
419+
],
420+
));
421+
}
382422

383423
// Next, populate basic chains with forwarding rules
384424
// Input chain: ip saddr <subnet> udp dport 53 accept

src/firewall/state.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,9 @@ mod tests {
272272
network_hash_name: "hash".to_string(),
273273
isolation: IsolateOption::Never,
274274
dns_port: 53,
275+
outbound_addr: None,
275276
};
276-
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}"#;
277+
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,"outbound_addr":null}"#;
277278

278279
let port_conf = PortForwardConfig {
279280
container_id: container_id.to_string(),

src/firewall/varktables/types.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ pub fn get_network_chains<'a>(
200200
interface_name: String,
201201
isolation: IsolateOption,
202202
dns_port: u16,
203+
outbound_addr: Option<IpAddr>,
203204
) -> Vec<VarkChain<'a>> {
204205
let mut chains = Vec::new();
205206
let prefixed_network_hash_name = format!("{}-{}", "NETAVARK", network_hash_name);
@@ -222,10 +223,27 @@ pub fn get_network_chains<'a>(
222223
if is_ipv6 {
223224
multicast_dest = MULTICAST_NET_V6;
224225
}
225-
hashed_network_chain.build_rule(VarkRule::new(
226-
format!("! -d {multicast_dest} -j {MASQUERADE}"),
227-
Some(TeardownPolicy::OnComplete),
228-
));
226+
if let Some(addr) = outbound_addr {
227+
if !is_ipv6 && addr.is_ipv4() {
228+
log::trace!("Creating SNAT rule with outbound address {}", addr);
229+
hashed_network_chain.build_rule(VarkRule::new(
230+
format!("! -d {multicast_dest} -j SNAT --to-source {}", addr),
231+
Some(TeardownPolicy::OnComplete),
232+
));
233+
} else {
234+
log::trace!("Outbound address {} is not IPv4, using default MASQUERADE rule", addr);
235+
hashed_network_chain.build_rule(VarkRule::new(
236+
format!("! -d {multicast_dest} -j {MASQUERADE}"),
237+
Some(TeardownPolicy::OnComplete),
238+
));
239+
}
240+
} else {
241+
log::trace!("No outbound address set, using default MASQUERADE rule");
242+
hashed_network_chain.build_rule(VarkRule::new(
243+
format!("! -d {multicast_dest} -j {MASQUERADE}"),
244+
Some(TeardownPolicy::OnComplete),
245+
));
246+
}
229247
chains.push(hashed_network_chain);
230248

231249
// POSTROUTING

src/network/bridge.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ impl<'a> Bridge<'a> {
392392
network_hash_name: id_network_hash.clone(),
393393
isolation: isolate,
394394
dns_port: self.info.dns_port,
395+
outbound_addr: self.info.network.outbound_addr,
395396
};
396397

397398
let mut has_ipv4 = false;

src/network/constants.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub const OPTION_BCLIM: &str = "bclim";
2424
pub const OPTION_VRF: &str = "vrf";
2525
pub const OPTION_VLAN: &str = "vlan";
2626
pub const OPTION_HOST_INTERFACE_NAME: &str = "host_interface_name";
27+
pub const OPTION_OUTBOUND_ADDR: &str = "outbound_addr";
2728

2829
/// 100 is the default metric for most Linux networking tools.
2930
pub const DEFAULT_METRIC: u32 = 100;

src/network/internal_types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub struct SetupNetwork {
2626
pub isolation: IsolateOption,
2727
/// port used for the dns server
2828
pub dns_port: u16,
29+
/// outbound address for SNAT
30+
pub outbound_addr: Option<IpAddr>,
2931
}
3032

3133
#[derive(Debug)]

src/network/types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ pub struct Network {
5757
/// Network DNS servers for aardvark-dns.
5858
#[serde(rename = "network_dns_servers")]
5959
pub network_dns_servers: Option<Vec<IpAddr>>,
60+
61+
/// outbound addr for this network.
62+
#[serde(rename = "outbound_addr")]
63+
pub outbound_addr: Option<IpAddr>,
6064
}
6165

6266
/// NetworkOptions for a given container.

0 commit comments

Comments
 (0)