Skip to main content

lab_ops_natmap/
policy_route.rs

1use std::process::Command;
2
3use color_eyre::Result;
4use color_eyre::eyre::WrapErr;
5
6use crate::models::PolicyRouteConfig;
7
8pub struct PolicyRouteManager;
9
10impl PolicyRouteManager {
11    pub fn new() -> Self {
12        Self
13    }
14
15    fn check_rule_exists(&self, config: &PolicyRouteConfig) -> Result<bool> {
16        let output = Command::new("ip")
17            .args(["rule", "show"])
18            .output()
19            .wrap_err("Failed to execute ip rule show")?;
20
21        let stdout = String::from_utf8_lossy(&output.stdout);
22        // Format: "32765:  from <src_ip> lookup <table>"
23        let expected = format!("from {} lookup {}", config.src_ip, config.table);
24        Ok(stdout.contains(&expected))
25    }
26
27    fn check_route_exists(&self, config: &PolicyRouteConfig) -> Result<bool> {
28        let output = Command::new("ip")
29            .args(["route", "show", "table", &config.table.to_string()])
30            .output()
31            .wrap_err("Failed to execute ip route show")?;
32
33        let stdout = String::from_utf8_lossy(&output.stdout);
34        let expected = format!("default via {}", config.via);
35        Ok(stdout.contains(&expected))
36    }
37
38    pub fn install(&self, config: &PolicyRouteConfig) -> Result<()> {
39        if !self.check_route_exists(config)? {
40            let status = Command::new("ip")
41                .args([
42                    "route",
43                    "add",
44                    "default",
45                    "via",
46                    &config.via,
47                    "table",
48                    &config.table.to_string(),
49                ])
50                .status()
51                .wrap_err("Failed to execute ip route add")?;
52
53            if !status.success() {
54                color_eyre::eyre::bail!("ip route add failed with status: {}", status);
55            }
56        }
57
58        if !self.check_rule_exists(config)? {
59            let status = Command::new("ip")
60                .args([
61                    "rule",
62                    "add",
63                    "from",
64                    &config.src_ip,
65                    "table",
66                    &config.table.to_string(),
67                ])
68                .status()
69                .wrap_err("Failed to execute ip rule add")?;
70
71            if !status.success() {
72                color_eyre::eyre::bail!("ip rule add failed with status: {}", status);
73            }
74        }
75
76        Ok(())
77    }
78
79    pub fn remove(&self, config: &PolicyRouteConfig) -> Result<()> {
80        // We ignore errors on remove, in case the rules are already gone.
81        let _ = Command::new("ip")
82            .args([
83                "rule",
84                "del",
85                "from",
86                &config.src_ip,
87                "table",
88                &config.table.to_string(),
89            ])
90            .status();
91
92        let _ = Command::new("ip")
93            .args([
94                "route",
95                "del",
96                "default",
97                "via",
98                &config.via,
99                "table",
100                &config.table.to_string(),
101            ])
102            .status();
103
104        Ok(())
105    }
106
107    pub fn flush_all(&self, policy_routes: &[PolicyRouteConfig]) -> Result<()> {
108        for config in policy_routes {
109            self.remove(config)?;
110        }
111        Ok(())
112    }
113}