sandbox_runtime/sandbox/linux/
bridge.rs

1//! Socat Unix socket bridges for Linux network sandboxing.
2
3use std::path::PathBuf;
4use std::process::Stdio;
5
6use tokio::process::{Child, Command};
7
8use crate::error::SandboxError;
9
10/// A socat bridge between a Unix socket and a TCP port.
11pub struct SocatBridge {
12    child: Option<Child>,
13    socket_path: PathBuf,
14}
15
16impl SocatBridge {
17    /// Create a bridge from a Unix socket to a TCP port.
18    /// The Unix socket will be created and listen for connections.
19    /// Each connection will be forwarded to the TCP port.
20    pub async fn unix_to_tcp(
21        socket_path: PathBuf,
22        tcp_host: &str,
23        tcp_port: u16,
24    ) -> Result<Self, SandboxError> {
25        // Remove existing socket if present
26        if socket_path.exists() {
27            std::fs::remove_file(&socket_path)?;
28        }
29
30        let child = Command::new("socat")
31            .args([
32                &format!("UNIX-LISTEN:{},fork", socket_path.display()),
33                &format!("TCP:{}:{}", tcp_host, tcp_port),
34            ])
35            .stdin(Stdio::null())
36            .stdout(Stdio::null())
37            .stderr(Stdio::null())
38            .spawn()
39            .map_err(|e| {
40                if e.kind() == std::io::ErrorKind::NotFound {
41                    SandboxError::MissingDependency(
42                        "socat not found. Please install socat.".to_string(),
43                    )
44                } else {
45                    SandboxError::Io(e)
46                }
47            })?;
48
49        // Wait a bit for the socket to be created
50        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
51
52        Ok(Self {
53            child: Some(child),
54            socket_path,
55        })
56    }
57
58    /// Create a bridge from a TCP port to a Unix socket.
59    /// This is used inside the sandbox to connect to the host proxies.
60    pub fn tcp_to_unix_command(tcp_port: u16, socket_path: &str) -> String {
61        format!(
62            "socat TCP-LISTEN:{},fork,reuseaddr UNIX-CONNECT:{}",
63            tcp_port, socket_path
64        )
65    }
66
67    /// Get the socket path.
68    pub fn socket_path(&self) -> &PathBuf {
69        &self.socket_path
70    }
71
72    /// Stop the bridge.
73    pub async fn stop(&mut self) {
74        if let Some(ref mut child) = self.child {
75            let _ = child.kill().await;
76        }
77
78        // Clean up socket
79        if self.socket_path.exists() {
80            let _ = std::fs::remove_file(&self.socket_path);
81        }
82    }
83}
84
85impl Drop for SocatBridge {
86    fn drop(&mut self) {
87        if let Some(ref mut child) = self.child {
88            let _ = child.start_kill();
89        }
90
91        if self.socket_path.exists() {
92            let _ = std::fs::remove_file(&self.socket_path);
93        }
94    }
95}
96
97/// Check if socat is available.
98pub fn check_socat() -> bool {
99    std::process::Command::new("socat")
100        .arg("-V")
101        .output()
102        .map(|o| o.status.success())
103        .unwrap_or(false)
104}
105
106/// Generate a unique socket path in /tmp.
107pub fn generate_socket_path(prefix: &str) -> PathBuf {
108    use rand::Rng;
109    let mut rng = rand::thread_rng();
110    let suffix: u32 = rng.gen();
111    PathBuf::from(format!("/tmp/{}-{}-{:08x}.sock", prefix, std::process::id(), suffix))
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_generate_socket_path() {
120        let path1 = generate_socket_path("srt-http");
121        let path2 = generate_socket_path("srt-http");
122
123        assert!(path1.to_string_lossy().starts_with("/tmp/srt-http-"));
124        assert!(path1.to_string_lossy().ends_with(".sock"));
125        // Paths should be different due to random suffix
126        assert_ne!(path1, path2);
127    }
128
129    #[test]
130    fn test_tcp_to_unix_command() {
131        let cmd = SocatBridge::tcp_to_unix_command(3128, "/tmp/http.sock");
132        assert_eq!(
133            cmd,
134            "socat TCP-LISTEN:3128,fork,reuseaddr UNIX-CONNECT:/tmp/http.sock"
135        );
136    }
137}