Skip to content

Commit 6d0c2ae

Browse files
committed
add outbound_addr4 & outbound_addr6 to bridge driver
Signed-off-by: lto-dev <[email protected]>
1 parent 9ad5cb6 commit 6d0c2ae

File tree

11 files changed

+378
-75
lines changed

11 files changed

+378
-75
lines changed

src/firewall/iptables.rs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use crate::firewall;
33
use crate::firewall::firewalld;
44
use crate::firewall::varktables::types::TeardownPolicy::OnComplete;
55
use crate::firewall::varktables::types::{
6-
create_network_chains, get_network_chains, get_port_forwarding_chains, TeardownPolicy,
6+
create_network_chains, get_network_chains, get_port_forwarding_chains, NetworkChainConfig,
7+
TeardownPolicy,
78
};
89
use crate::network::internal_types::{
910
PortForwardConfig, SetupNetwork, TearDownNetwork, TeardownPortForward,
@@ -54,15 +55,16 @@ impl firewall::FirewallDriver for IptablesDriver {
5455
conn = &self.conn6;
5556
}
5657

57-
let chains = get_network_chains(
58-
conn,
58+
let config = NetworkChainConfig {
5959
network,
60-
&network_setup.network_hash_name,
61-
is_ipv6,
62-
network_setup.bridge_name.clone(),
63-
network_setup.isolation,
64-
network_setup.dns_port,
65-
);
60+
network_hash_name: network_setup.network_hash_name.clone(),
61+
interface_name: network_setup.bridge_name.clone(),
62+
isolation: network_setup.isolation,
63+
dns_port: network_setup.dns_port,
64+
outbound_addr4: network_setup.outbound_addr4,
65+
outbound_addr6: network_setup.outbound_addr6,
66+
};
67+
let chains = get_network_chains(conn, config);
6668

6769
create_network_chains(chains)?;
6870

@@ -83,15 +85,16 @@ impl firewall::FirewallDriver for IptablesDriver {
8385
if is_ipv6 {
8486
conn = &self.conn6;
8587
}
86-
let chains = get_network_chains(
87-
conn,
88+
let config = NetworkChainConfig {
8889
network,
89-
&tear.config.network_hash_name,
90-
is_ipv6,
91-
tear.config.bridge_name.clone(),
92-
tear.config.isolation,
93-
tear.config.dns_port,
94-
);
90+
network_hash_name: tear.config.network_hash_name.clone(),
91+
interface_name: tear.config.bridge_name.clone(),
92+
isolation: tear.config.isolation,
93+
dns_port: tear.config.dns_port,
94+
outbound_addr4: tear.config.outbound_addr4,
95+
outbound_addr6: tear.config.outbound_addr6,
96+
};
97+
let chains = get_network_chains(conn, config);
9598

9699
for c in &chains {
97100
c.remove_rules(tear.complete_teardown)?;

src/firewall/nft.rs

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ const ISOLATION3CHAIN: &str = "NETAVARK-ISOLATION-3";
3131

3232
const MASK: u32 = 0x2000;
3333

34+
const MULTICAST_NET_V4: &str = "224.0.0.0/4";
35+
const MULTICAST_NET_V6: &str = "ff00::/8";
36+
3437
/// The dnat priority for chains
3538
/// This (and the below) are based on https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
3639
const DNATPRIO: i32 = -100;
@@ -376,18 +379,91 @@ impl firewall::FirewallDriver for Nftables {
376379
]),
377380
));
378381

379-
// Subnet chain: ip daddr != 224.0.0.0/4 masquerade
382+
// Subnet chain: ip daddr != 224.0.0.0/4 snat/masquerade
380383
let multicast_address: IpNet = match subnet {
381-
IpNet::V4(_) => "224.0.0.0/4".parse()?,
382-
IpNet::V6(_) => "ff::00/8".parse()?,
384+
IpNet::V4(_) => MULTICAST_NET_V4.parse()?,
385+
IpNet::V6(_) => MULTICAST_NET_V6.parse()?,
383386
};
384-
batch.add(make_rule(
385-
chain.clone(),
386-
Cow::Owned(vec![
387-
get_subnet_match(&multicast_address, "daddr", stmt::Operator::NEQ),
388-
stmt::Statement::Masquerade(None),
389-
]),
390-
));
387+
388+
// Use appropriate outbound address based on subnet type
389+
match subnet {
390+
IpNet::V4(_) => {
391+
if let Some(addr4) = network_setup.outbound_addr4 {
392+
log::trace!("Creating IPv4 SNAT rule with outbound address {}", addr4);
393+
batch.add(make_rule(
394+
chain.clone(),
395+
Cow::Owned(vec![
396+
get_subnet_match(
397+
&multicast_address,
398+
"daddr",
399+
stmt::Operator::NEQ,
400+
),
401+
stmt::Statement::SNAT(Some(stmt::NAT {
402+
addr: Some(expr::Expression::String(
403+
addr4.to_string().into(),
404+
)),
405+
family: Some(stmt::NATFamily::IP),
406+
port: None,
407+
flags: None,
408+
})),
409+
]),
410+
));
411+
} else {
412+
log::trace!(
413+
"No IPv4 outbound address set, using default MASQUERADE rule"
414+
);
415+
batch.add(make_rule(
416+
chain.clone(),
417+
Cow::Owned(vec![
418+
get_subnet_match(
419+
&multicast_address,
420+
"daddr",
421+
stmt::Operator::NEQ,
422+
),
423+
stmt::Statement::Masquerade(None),
424+
]),
425+
));
426+
}
427+
}
428+
IpNet::V6(_) => {
429+
if let Some(addr6) = network_setup.outbound_addr6 {
430+
log::trace!("Creating IPv6 SNAT rule with outbound address {}", addr6);
431+
batch.add(make_rule(
432+
chain.clone(),
433+
Cow::Owned(vec![
434+
get_subnet_match(
435+
&multicast_address,
436+
"daddr",
437+
stmt::Operator::NEQ,
438+
),
439+
stmt::Statement::SNAT(Some(stmt::NAT {
440+
addr: Some(expr::Expression::String(
441+
addr6.to_string().into(),
442+
)),
443+
family: Some(stmt::NATFamily::IP6),
444+
port: None,
445+
flags: None,
446+
})),
447+
]),
448+
));
449+
} else {
450+
log::trace!(
451+
"No IPv6 outbound address set, using default MASQUERADE rule"
452+
);
453+
batch.add(make_rule(
454+
chain.clone(),
455+
Cow::Owned(vec![
456+
get_subnet_match(
457+
&multicast_address,
458+
"daddr",
459+
stmt::Operator::NEQ,
460+
),
461+
stmt::Statement::Masquerade(None),
462+
]),
463+
));
464+
}
465+
}
466+
}
391467

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

src/firewall/state.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ mod tests {
272272
network_hash_name: "hash".to_string(),
273273
isolation: IsolateOption::Never,
274274
dns_port: 53,
275+
outbound_addr4: None,
276+
outbound_addr6: None,
275277
};
276278
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}"#;
277279

src/firewall/varktables/types.rs

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::network::internal_types::{IsolateOption, PortForwardConfig};
77
use ipnet::IpNet;
88
use iptables::IPTables;
99
use log::debug;
10-
use std::net::IpAddr;
10+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
1111

1212
// Chain names
1313
const NAT: &str = "nat";
@@ -66,6 +66,7 @@ impl VarkRule {
6666
&self.rule
6767
}
6868
}
69+
6970
// Varkchain is an iptable chain with extra info
7071
pub struct VarkChain<'a> {
7172
// name of chain
@@ -192,17 +193,24 @@ pub fn create_network_chains(chains: Vec<VarkChain<'_>>) -> NetavarkResult<()> {
192193
Ok(())
193194
}
194195

195-
pub fn get_network_chains<'a>(
196-
conn: &'a IPTables,
197-
network: IpNet,
198-
network_hash_name: &'a str,
199-
is_ipv6: bool,
200-
interface_name: String,
201-
isolation: IsolateOption,
202-
dns_port: u16,
203-
) -> Vec<VarkChain<'a>> {
196+
pub struct NetworkChainConfig {
197+
pub network: IpNet,
198+
pub network_hash_name: String,
199+
pub interface_name: String,
200+
pub isolation: IsolateOption,
201+
pub dns_port: u16,
202+
pub outbound_addr4: Option<Ipv4Addr>,
203+
pub outbound_addr6: Option<Ipv6Addr>,
204+
}
205+
206+
pub fn get_network_chains(conn: &IPTables, config: NetworkChainConfig) -> Vec<VarkChain<'_>> {
204207
let mut chains = Vec::new();
205-
let prefixed_network_hash_name = format!("{}-{}", "NETAVARK", network_hash_name);
208+
let prefixed_network_hash_name = format!("{}-{}", "NETAVARK", config.network_hash_name);
209+
210+
let is_ipv6 = match config.network {
211+
IpNet::V4(_) => false,
212+
IpNet::V6(_) => true,
213+
};
206214

207215
// NETAVARK-HASH
208216
let mut hashed_network_chain = VarkChain::new(
@@ -214,25 +222,52 @@ pub fn get_network_chains<'a>(
214222
hashed_network_chain.create = true;
215223

216224
hashed_network_chain.build_rule(VarkRule::new(
217-
format!("-d {network} -j {ACCEPT}"),
225+
format!("-d {} -j {}", config.network, ACCEPT),
218226
Some(TeardownPolicy::OnComplete),
219227
));
220228

221229
let mut multicast_dest = MULTICAST_NET_V4;
222230
if is_ipv6 {
223231
multicast_dest = MULTICAST_NET_V6;
224232
}
225-
hashed_network_chain.build_rule(VarkRule::new(
226-
format!("! -d {multicast_dest} -j {MASQUERADE}"),
227-
Some(TeardownPolicy::OnComplete),
228-
));
233+
234+
// Use appropriate outbound address based on subnet type
235+
if is_ipv6 {
236+
if let Some(addr6) = config.outbound_addr6 {
237+
log::trace!("Creating IPv6 SNAT rule with outbound address {}", addr6);
238+
hashed_network_chain.build_rule(VarkRule::new(
239+
format!("! -d {multicast_dest} -j SNAT --to-source {}", addr6),
240+
Some(TeardownPolicy::OnComplete),
241+
));
242+
} else {
243+
log::trace!("No IPv6 outbound address set, using default MASQUERADE rule");
244+
hashed_network_chain.build_rule(VarkRule::new(
245+
format!("! -d {multicast_dest} -j {MASQUERADE}"),
246+
Some(TeardownPolicy::OnComplete),
247+
));
248+
}
249+
} else {
250+
if let Some(addr4) = config.outbound_addr4 {
251+
log::trace!("Creating IPv4 SNAT rule with outbound address {}", addr4);
252+
hashed_network_chain.build_rule(VarkRule::new(
253+
format!("! -d {multicast_dest} -j SNAT --to-source {}", addr4),
254+
Some(TeardownPolicy::OnComplete),
255+
));
256+
} else {
257+
log::trace!("No IPv4 outbound address set, using default MASQUERADE rule");
258+
hashed_network_chain.build_rule(VarkRule::new(
259+
format!("! -d {multicast_dest} -j {MASQUERADE}"),
260+
Some(TeardownPolicy::OnComplete),
261+
));
262+
}
263+
}
229264
chains.push(hashed_network_chain);
230265

231266
// POSTROUTING
232267
let mut postrouting_chain =
233268
VarkChain::new(conn, NAT.to_string(), POSTROUTING.to_string(), None);
234269
postrouting_chain.build_rule(VarkRule::new(
235-
format!("-s {network} -j {prefixed_network_hash_name}"),
270+
format!("-s {} -j {}", config.network, prefixed_network_hash_name),
236271
Some(TeardownPolicy::OnComplete),
237272
));
238273
chains.push(postrouting_chain);
@@ -272,7 +307,7 @@ pub fn get_network_chains<'a>(
272307
);
273308
netavark_isolation_chain_3.create = true;
274309

275-
if let IsolateOption::Normal | IsolateOption::Strict = isolation {
310+
if let IsolateOption::Normal | IsolateOption::Strict = config.isolation {
276311
debug!("Add extra isolate rules");
277312
// NETAVARK_ISOLATION_1
278313
let mut netavark_isolation_chain_1 = VarkChain::new(
@@ -290,7 +325,7 @@ pub fn get_network_chains<'a>(
290325
td_policy: Some(TeardownPolicy::OnComplete),
291326
});
292327

293-
let netavark_isolation_1_target = if let IsolateOption::Strict = isolation {
328+
let netavark_isolation_1_target = if let IsolateOption::Strict = config.isolation {
294329
// NETAVARK_ISOLATION_1 -i bridge_name ! -o bridge_name -j NETAVARK_ISOLATION_3
295330
NETAVARK_ISOLATION_3
296331
} else {
@@ -299,15 +334,16 @@ pub fn get_network_chains<'a>(
299334
};
300335
netavark_isolation_chain_1.build_rule(VarkRule {
301336
rule: format!(
302-
"-i {interface_name} ! -o {interface_name} -j {netavark_isolation_1_target}"
337+
"-i {} ! -o {} -j {}",
338+
config.interface_name, config.interface_name, netavark_isolation_1_target
303339
),
304340
position: Some(ind),
305341
td_policy: Some(TeardownPolicy::OnComplete),
306342
});
307343

308344
// NETAVARK_ISOLATION_2 -o bridge_name -j DROP
309345
netavark_isolation_chain_2.build_rule(VarkRule {
310-
rule: format!("-o {} -j {}", interface_name, "DROP"),
346+
rule: format!("-o {} -j {}", config.interface_name, "DROP"),
311347
position: Some(ind),
312348
td_policy: Some(TeardownPolicy::OnComplete),
313349
});
@@ -328,7 +364,7 @@ pub fn get_network_chains<'a>(
328364

329365
// NETAVARK_ISOLATION_3 -o bridge_name -j DROP
330366
netavark_isolation_chain_3.build_rule(VarkRule {
331-
rule: format!("-o {} -j {}", interface_name, "DROP"),
367+
rule: format!("-o {} -j {}", config.interface_name, "DROP"),
332368
position: Some(ind),
333369
td_policy: Some(TeardownPolicy::OnComplete),
334370
});
@@ -375,7 +411,10 @@ pub fn get_network_chains<'a>(
375411
// to gateway when using bridge network with internal dns.
376412
for proto in ["udp", "tcp"] {
377413
netavark_input_chain.build_rule(VarkRule::new(
378-
format!("-p {proto} -s {network} --dport {dns_port} -j {ACCEPT}"),
414+
format!(
415+
"-p {proto} -s {} --dport {} -j {ACCEPT}",
416+
config.network, config.dns_port
417+
),
379418
Some(TeardownPolicy::OnComplete),
380419
));
381420
}
@@ -392,14 +431,17 @@ pub fn get_network_chains<'a>(
392431
// Create incoming traffic rule
393432
// CNI did this by IP address, this is implemented per subnet
394433
netavark_forward_chain.build_rule(VarkRule::new(
395-
format!("-d {network} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT"),
434+
format!(
435+
"-d {} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT",
436+
config.network
437+
),
396438
Some(TeardownPolicy::OnComplete),
397439
));
398440

399441
// Create outgoing traffic rule
400442
// CNI did this by IP address, this is implemented per subnet
401443
netavark_forward_chain.build_rule(VarkRule::new(
402-
format!("-s {network} -j ACCEPT"),
444+
format!("-s {} -j ACCEPT", config.network),
403445
Some(TeardownPolicy::OnComplete),
404446
));
405447
chains.push(netavark_forward_chain);

0 commit comments

Comments
 (0)