Skip to main content

icebox/core/proxy/
netns.rs

1use async_trait::async_trait;
2use std::process::Command;
3use tracing::info;
4
5use super::{NetworkIsolator, ProxyListener};
6
7pub struct LinuxNetnsIsolator {
8    pub namespace_name: String,
9}
10
11#[async_trait]
12impl NetworkIsolator for LinuxNetnsIsolator {
13    async fn setup(&self) -> anyhow::Result<()> {
14        if !cfg!(target_os = "linux") {
15            anyhow::bail!("Linux netns isolator is only supported on Linux");
16        }
17
18        info!("Setting up network namespace: {}", self.namespace_name);
19
20        run_cmd("ip", &["netns", "add", &self.namespace_name])?;
21
22        let veth_host = format!(
23            "veth-{}-h",
24            &self.namespace_name[..std::cmp::min(self.namespace_name.len(), 4)]
25        );
26        let veth_guest = format!(
27            "veth-{}-g",
28            &self.namespace_name[..std::cmp::min(self.namespace_name.len(), 4)]
29        );
30
31        run_cmd(
32            "ip",
33            &[
34                "link",
35                "add",
36                &veth_host,
37                "type",
38                "veth",
39                "peer",
40                "name",
41                &veth_guest,
42            ],
43        )?;
44
45        run_cmd(
46            "ip",
47            &["link", "set", &veth_guest, "netns", &self.namespace_name],
48        )?;
49
50        run_cmd("ip", &["addr", "add", "10.0.0.1/24", "dev", &veth_host])?;
51        run_cmd("ip", &["link", "set", &veth_host, "up"])?;
52
53        run_cmd(
54            "ip",
55            &[
56                "netns",
57                "exec",
58                &self.namespace_name,
59                "ip",
60                "addr",
61                "add",
62                "10.0.0.2/24",
63                "dev",
64                &veth_guest,
65            ],
66        )?;
67        run_cmd(
68            "ip",
69            &[
70                "netns",
71                "exec",
72                &self.namespace_name,
73                "ip",
74                "link",
75                "set",
76                &veth_guest,
77                "up",
78            ],
79        )?;
80
81        run_cmd(
82            "ip",
83            &[
84                "netns",
85                "exec",
86                &self.namespace_name,
87                "ip",
88                "route",
89                "add",
90                "default",
91                "via",
92                "10.0.0.1",
93            ],
94        )?;
95
96        run_cmd(
97            "ip",
98            &[
99                "netns",
100                "exec",
101                &self.namespace_name,
102                "iptables",
103                "-t",
104                "nat",
105                "-A",
106                "OUTPUT",
107                "-p",
108                "udp",
109                "--dport",
110                "53",
111                "-j",
112                "DNAT",
113                "--to-destination",
114                "10.0.0.1:53",
115            ],
116        )?;
117
118        Ok(())
119    }
120
121    async fn teardown(&self) -> anyhow::Result<()> {
122        if !cfg!(target_os = "linux") {
123            return Ok(());
124        }
125
126        info!("Tearing down network namespace: {}", self.namespace_name);
127        let _ = run_cmd("ip", &["netns", "delete", &self.namespace_name]);
128        Ok(())
129    }
130
131    async fn spawn_proxy(
132        &self,
133        target_ip: &str,
134        target_port: u16,
135    ) -> anyhow::Result<(ProxyListener, tokio::task::JoinHandle<()>)> {
136        let tcp_isolator = super::tcp::TcpProxyIsolator;
137        let (proxy, handle) = tcp_isolator.spawn_proxy(target_ip, target_port).await?;
138
139        tokio::spawn(async move {
140            if let Ok(socket) = tokio::net::UdpSocket::bind("10.0.0.1:53").await {
141                tracing::info!("DNS Interceptor listening on 10.0.0.1:53");
142                let mut buf = [0u8; 512];
143                while let Ok((size, peer)) = socket.recv_from(&mut buf).await {
144                    tracing::info!("Intercepted DNS query from {} size {}", peer, size);
145                    if size >= 12 {
146                        if let Ok(upstream) = tokio::net::UdpSocket::bind("0.0.0.0:0").await {
147                            let _ = upstream.send_to(&buf[..size], "8.8.8.8:53").await;
148                            let mut resp_buf = [0u8; 512];
149                            if let Ok((resp_size, _)) = tokio::time::timeout(
150                                std::time::Duration::from_secs(2),
151                                upstream.recv_from(&mut resp_buf),
152                            )
153                            .await
154                            .unwrap_or(Err(std::io::Error::new(
155                                std::io::ErrorKind::TimedOut,
156                                "timeout",
157                            ))) {
158                                let _ = socket.send_to(&resp_buf[..resp_size], peer).await;
159                            }
160                        }
161                    }
162                }
163            }
164        });
165
166        Ok((proxy, handle))
167    }
168}
169
170fn run_cmd(cmd: &str, args: &[&str]) -> anyhow::Result<()> {
171    let status = Command::new(cmd).args(args).status()?;
172    if !status.success() {
173        anyhow::bail!(
174            "Command `{} {:?}` failed with status: {}",
175            cmd,
176            args,
177            status
178        );
179    }
180    Ok(())
181}