Skip to main content

shuru_proxy/
lib.rs

1pub mod config;
2mod device;
3mod dns;
4mod proxy;
5mod stack;
6mod stream;
7mod tls;
8
9pub use config::ProxyConfig;
10
11use std::collections::HashMap;
12use std::os::unix::io::RawFd;
13
14use proxy::ProxyEngine;
15use stack::NetworkStack;
16use tls::CertificateAuthority;
17use tokio::sync::mpsc;
18use tracing::info;
19
20/// Handle to a running proxy. Shuts down on drop.
21pub struct ProxyHandle {
22    _stack_thread: std::thread::JoinHandle<()>,
23    _runtime_thread: std::thread::JoinHandle<()>,
24    /// Placeholder tokens generated for secrets. Key = env var name, Value = placeholder.
25    pub placeholders: HashMap<String, String>,
26    /// CA certificate in PEM format (for injecting into guest trust store).
27    pub ca_cert_pem: Vec<u8>,
28}
29
30/// Generate a unique placeholder token for a secret.
31fn generate_placeholder() -> String {
32    use std::sync::atomic::{AtomicU64, Ordering};
33    use std::time::{SystemTime, UNIX_EPOCH};
34    static COUNTER: AtomicU64 = AtomicU64::new(0);
35    let ts = SystemTime::now()
36        .duration_since(UNIX_EPOCH)
37        .unwrap()
38        .as_nanos() as u64;
39    let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
40    format!("shuru_tok_{:016x}{:04x}", ts, seq)
41}
42
43/// Create a Unix datagram socketpair for VZFileHandleNetworkDeviceAttachment.
44/// Returns (vm_fd, host_fd). The vm_fd goes to VZ, host_fd goes to the proxy.
45pub fn create_socketpair() -> anyhow::Result<(RawFd, RawFd)> {
46    let mut fds = [0i32; 2];
47    let ret = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_DGRAM, 0, fds.as_mut_ptr()) };
48    if ret != 0 {
49        return Err(anyhow::anyhow!(
50            "socketpair failed: {}",
51            std::io::Error::last_os_error()
52        ));
53    }
54
55    let host_fd = fds[1];
56
57    // Apple recommends SO_RCVBUF >= 2x SO_SNDBUF for VZFileHandleNetworkDeviceAttachment
58    unsafe {
59        let sndbuf: libc::c_int = 1024 * 1024;
60        let rcvbuf: libc::c_int = 4 * 1024 * 1024;
61        libc::setsockopt(
62            host_fd,
63            libc::SOL_SOCKET,
64            libc::SO_SNDBUF,
65            &sndbuf as *const _ as _,
66            std::mem::size_of::<libc::c_int>() as _,
67        );
68        libc::setsockopt(
69            host_fd,
70            libc::SOL_SOCKET,
71            libc::SO_RCVBUF,
72            &rcvbuf as *const _ as _,
73            std::mem::size_of::<libc::c_int>() as _,
74        );
75    }
76
77    Ok((fds[0], fds[1]))
78}
79
80/// Start the proxy engine. Returns a handle that keeps it running.
81///
82/// - `host_fd`: the host end of the socketpair (raw L2 Ethernet frames)
83/// - `config`: proxy configuration (secrets, network rules)
84pub fn start(host_fd: RawFd, config: ProxyConfig) -> anyhow::Result<ProxyHandle> {
85    // Install rustls crypto provider (process-wide, idempotent)
86    let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
87
88    let ca = CertificateAuthority::new()?;
89    let ca_cert_pem = ca.ca_cert_pem();
90
91    // Generate placeholder tokens for each secret
92    let mut placeholders = HashMap::new();
93    for name in config.secrets.keys() {
94        placeholders.insert(name.clone(), generate_placeholder());
95    }
96
97    let (event_tx, event_rx) = mpsc::unbounded_channel();
98    let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
99
100    let stack_thread = std::thread::Builder::new()
101        .name("shuru-netstack".into())
102        .spawn(move || {
103            let mut stack = NetworkStack::new(host_fd, event_tx, cmd_rx);
104            stack.run();
105        })?;
106
107    let proxy_config = config;
108    let proxy_placeholders = placeholders.clone();
109    let runtime_thread = std::thread::Builder::new()
110        .name("shuru-proxy".into())
111        .spawn(move || {
112            let rt = tokio::runtime::Builder::new_multi_thread()
113                .worker_threads(2)
114                .enable_all()
115                .build()
116                .expect("failed to create tokio runtime for proxy");
117
118            rt.block_on(async move {
119                let mut engine =
120                    ProxyEngine::new(proxy_config, event_rx, cmd_tx, ca, proxy_placeholders);
121                engine.run().await;
122            });
123        })?;
124
125    info!("proxy started");
126
127    Ok(ProxyHandle {
128        _stack_thread: stack_thread,
129        _runtime_thread: runtime_thread,
130        placeholders,
131        ca_cert_pem,
132    })
133}