isoloom_render/network/
router.rs1use std::net::Ipv4Addr;
13
14pub use isoloom_plan::routing::{NAME, address, default_gateway, is_gateway, needed, networks, plain, routed, routes};
15use isoloom_spec::model::{Machine, Spec};
16
17use super::ipv6;
18
19#[must_use]
22pub fn nftables(spec: &Spec) -> String {
23 let body = forward_rules(spec).iter().map(|r| format!(" {r}")).collect::<Vec<_>>().join("\n");
24 format!("table inet isoloom {{\n chain forward {{\n type filter hook forward priority 0; policy drop;\n{body}\n }}\n}}\n")
25}
26
27#[must_use]
30pub fn forward_rules(spec: &Spec) -> Vec<String> {
31 let mut rules = vec!["ct state established,related accept".to_owned()];
32 for r in spec.reach.iter().filter(|r| plain(spec, &r.from) && plain(spec, &r.to)) {
33 let (Some(from), Some(to)) = (spec.networks.get(&r.from), spec.networks.get(&r.to)) else {
34 continue;
35 };
36 let (from, to) = (&from.cidr, &to.cidr);
37 if r.ports.is_empty() {
38 rules.push(format!("ip saddr {from} ip daddr {to} accept"));
39 } else {
40 let ports = r.ports.iter().map(u16::to_string).collect::<Vec<_>>().join(", ");
41 rules.push(format!(
42 "ip saddr {from} ip daddr {to} meta l4proto {{ tcp, udp }} th dport {{ {ports} }} accept"
43 ));
44 rules.push(format!("ip saddr {from} ip daddr {to} icmp type echo-request accept"));
45 }
46 rules.extend(ipv6::forward_rules(spec, &r.from, &r.to, &r.ports));
47 }
48 rules
49}
50
51#[must_use]
54pub fn tc_script(spec: &Spec) -> Option<String> {
55 netem_script(spec, networks(spec).map(|n| (n.as_str(), address(spec, n))))
56}
57
58#[must_use]
62pub fn machine_tc_script(spec: &Spec, m: &Machine, addr: impl Fn(&str, u8) -> Ipv4Addr) -> Option<String> {
63 netem_script(spec, m.networks.iter().map(|(n, octet)| (n.as_str(), addr(n, *octet))))
64}
65
66fn netem_script<'a>(spec: &Spec, at: impl Iterator<Item = (&'a str, Ipv4Addr)>) -> Option<String> {
68 let lines: Vec<String> = at
69 .filter_map(|(n, a)| spec.networks.get(n).and_then(|net| net.tc.as_ref()).map(|t| (a, t)))
70 .map(|(a, t)| {
71 let addr = a.to_string().replace('.', "\\.");
72 format!(
73 "IF=$(ip -o -4 addr show | awk '$4 ~ /^{addr}\\//{{print $2}}' | head -n 1); [ -n \"$IF\" ] && tc qdisc replace dev \"$IF\" root netem {}",
74 t.netem()
75 )
76 })
77 .collect();
78 (!lines.is_empty()).then(|| format!("{}\n", lines.join("\n")))
79}
80
81#[cfg(test)]
82mod tests;