OXYGENHUB

Coverage / crates/flow-init/src/egress.rs 0.0%

0 of 56 lines run, 0 of 8 functions · run #197 on main · run missed

1
//! The guest's half of allow-listed egress (oxygenflow#55): a proxy on
2
//! 127.0.0.1:3128 that relays each connection, unread, over vsock to the
3
//! runner (host CID 2), which decides what may pass. The VM has no network
4
//! device; this is its only way out.
5
6
use std::fs::File;
7
use std::io::{Read, Write};
8
use std::net::{TcpListener, TcpStream};
9
use std::os::fd::{AsRawFd, FromRawFd};
10
11
/// Where the job's tools reach the proxy, and the vsock port behind it.
12
const LISTEN: &str = "127.0.0.1:3128";
13
const VSOCK_PORT: u32 = 3128;
14
15
/// Start relaying in the background (before the job starts, so its first
16
/// connection finds the proxy listening).
170×
pub fn start() -> Result<(), String> {
180×
    let listener = TcpListener::bind(LISTEN).map_err(|e| format!("egress proxy on {LISTEN}: {e}"))?;
190×
    std::thread::spawn(move || {
200×
        for conn in listener.incoming().flatten() {
210×
            std::thread::spawn(move || relay(conn));
22
        }
230×
    });
240×
    Ok(())
250×
}
26
270×
fn vsock_to_host(port: u32) -> std::io::Result<File> {
280×
    let fd = unsafe { libc::socket(libc::AF_VSOCK, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0) };
290×
    if fd < 0 {
300×
        return Err(std::io::Error::last_os_error());
310×
    }
32
    // Owned from here, so it is closed on every path.
330×
    let file = unsafe { File::from_raw_fd(fd) };
340×
    let mut addr: libc::sockaddr_vm = unsafe { std::mem::zeroed() };
350×
    addr.svm_family = libc::AF_VSOCK as libc::sa_family_t;
360×
    addr.svm_cid = libc::VMADDR_CID_HOST;
370×
    addr.svm_port = port;
380×
    let rc = unsafe {
390×
        libc::connect(
400×
            fd,
410×
            &addr as *const libc::sockaddr_vm as *const libc::sockaddr,
420×
            std::mem::size_of::<libc::sockaddr_vm>() as libc::socklen_t,
43
        )
44
    };
450×
    if rc != 0 {
460×
        return Err(std::io::Error::last_os_error());
470×
    }
480×
    Ok(file)
490×
}
50
510×
fn relay(tcp: TcpStream) {
520×
    let Ok(host) = vsock_to_host(VSOCK_PORT) else {
530×
        let mut tcp = tcp;
540×
        let _ = tcp.write_all(b"HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\nthe runner's egress proxy did not answer\n");
550×
        return;
56
    };
570×
    let (Ok(mut tcp_r), Ok(mut host_w)) = (tcp.try_clone(), host.try_clone()) else { return };
580×
    let up = std::thread::spawn(move || {
590×
        copy(&mut tcp_r, &mut host_w);
600×
        unsafe { libc::shutdown(host_w.as_raw_fd(), libc::SHUT_WR) };
610×
    });
620×
    let (mut host_r, mut tcp_w) = (host, tcp);
630×
    copy(&mut host_r, &mut tcp_w);
640×
    let _ = tcp_w.shutdown(std::net::Shutdown::Write);
650×
    let _ = up.join();
660×
}
67
680×
fn copy(from: &mut impl Read, to: &mut impl Write) {
690×
    let mut buf = [0u8; 16 * 1024];
70
    loop {
710×
        match from.read(&mut buf) {
720×
            Ok(0) | Err(_) => return,
730×
            Ok(n) => {
740×
                if to.write_all(&buf[..n]).is_err() {
750×
                    return;
760×
                }
77
            }
78
        }
79
    }
800×
}