Skip to content

Commit 4f27a97

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

File tree

11 files changed

+377
-75
lines changed

11 files changed

+377
-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: 66 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,51 @@ 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 if let Some(addr4) = config.outbound_addr4 {
250+
log::trace!("Creating IPv4 SNAT rule with outbound address {addr4}");
251+
hashed_network_chain.build_rule(VarkRule::new(
252+
format!("! -d {multicast_dest} -j SNAT --to-source {addr4}"),
253+
Some(TeardownPolicy::OnComplete),
254+
));
255+
} else {
256+
log::trace!("No IPv4 outbound address set, using default MASQUERADE rule");
257+
hashed_network_chain.build_rule(VarkRule::new(
258+
format!("! -d {multicast_dest} -j {MASQUERADE}"),
259+
Some(TeardownPolicy::OnComplete),
260+
));
261+
}
262+
229263
chains.push(hashed_network_chain);
230264

231265
// POSTROUTING
232266
let mut postrouting_chain =
233267
VarkChain::new(conn, NAT.to_string(), POSTROUTING.to_string(), None);
234268
postrouting_chain.build_rule(VarkRule::new(
235-
format!("-s {network} -j {prefixed_network_hash_name}"),
269+
format!("-s {} -j {}", config.network, prefixed_network_hash_name),
236270
Some(TeardownPolicy::OnComplete),
237271
));
238272
chains.push(postrouting_chain);
@@ -272,7 +306,7 @@ pub fn get_network_chains<'a>(
272306
);
273307
netavark_isolation_chain_3.create = true;
274308

275-
if let IsolateOption::Normal | IsolateOption::Strict = isolation {
309+
if let IsolateOption::Normal | IsolateOption::Strict = config.isolation {
276310
debug!("Add extra isolate rules");
277311
// NETAVARK_ISOLATION_1
278312
let mut netavark_isolation_chain_1 = VarkChain::new(
@@ -290,7 +324,7 @@ pub fn get_network_chains<'a>(
290324
td_policy: Some(TeardownPolicy::OnComplete),
291325
});
292326

293-
let netavark_isolation_1_target = if let IsolateOption::Strict = isolation {
327+
let netavark_isolation_1_target = if let IsolateOption::Strict = config.isolation {
294328
// NETAVARK_ISOLATION_1 -i bridge_name ! -o bridge_name -j NETAVARK_ISOLATION_3
295329
NETAVARK_ISOLATION_3
296330
} else {
@@ -299,15 +333,16 @@ pub fn get_network_chains<'a>(
299333
};
300334
netavark_isolation_chain_1.build_rule(VarkRule {
301335
rule: format!(
302-
"-i {interface_name} ! -o {interface_name} -j {netavark_isolation_1_target}"
336+
"-i {} ! -o {} -j {}",
337+
config.interface_name, config.interface_name, netavark_isolation_1_target
303338
),
304339
position: Some(ind),
305340
td_policy: Some(TeardownPolicy::OnComplete),
306341
});
307342

308343
// NETAVARK_ISOLATION_2 -o bridge_name -j DROP
309344
netavark_isolation_chain_2.build_rule(VarkRule {
310-
rule: format!("-o {} -j {}", interface_name, "DROP"),
345+
rule: format!("-o {} -j {}", config.interface_name, "DROP"),
311346
position: Some(ind),
312347
td_policy: Some(TeardownPolicy::OnComplete),
313348
});
@@ -328,7 +363,7 @@ pub fn get_network_chains<'a>(
328363

329364
// NETAVARK_ISOLATION_3 -o bridge_name -j DROP
330365
netavark_isolation_chain_3.build_rule(VarkRule {
331-
rule: format!("-o {} -j {}", interface_name, "DROP"),
366+
rule: format!("-o {} -j {}", config.interface_name, "DROP"),
332367
position: Some(ind),
333368
td_policy: Some(TeardownPolicy::OnComplete),
334369
});
@@ -375,7 +410,10 @@ pub fn get_network_chains<'a>(
375410
// to gateway when using bridge network with internal dns.
376411
for proto in ["udp", "tcp"] {
377412
netavark_input_chain.build_rule(VarkRule::new(
378-
format!("-p {proto} -s {network} --dport {dns_port} -j {ACCEPT}"),
413+
format!(
414+
"-p {proto} -s {} --dport {} -j {ACCEPT}",
415+
config.network, config.dns_port
416+
),
379417
Some(TeardownPolicy::OnComplete),
380418
));
381419
}
@@ -392,14 +430,17 @@ pub fn get_network_chains<'a>(
392430
// Create incoming traffic rule
393431
// CNI did this by IP address, this is implemented per subnet
394432
netavark_forward_chain.build_rule(VarkRule::new(
395-
format!("-d {network} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT"),
433+
format!(
434+
"-d {} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT",
435+
config.network
436+
),
396437
Some(TeardownPolicy::OnComplete),
397438
));
398439

399440
// Create outgoing traffic rule
400441
// CNI did this by IP address, this is implemented per subnet
401442
netavark_forward_chain.build_rule(VarkRule::new(
402-
format!("-s {network} -j ACCEPT"),
443+
format!("-s {} -j ACCEPT", config.network),
403444
Some(TeardownPolicy::OnComplete),
404445
));
405446
chains.push(netavark_forward_chain);

0 commit comments

Comments
 (0)