forked from wrenn/wrenn
Complete Rust rewrite of the Go envd guest daemon that runs as PID 1 inside Firecracker microVMs. Feature-complete across all 8 phases: - Health, metrics, and env var endpoints - Crypto (SHA-256/512, HMAC), auth (secure token, signing), init/snapshot - Connect RPC via connectrpc + buffa (process + filesystem services) - File transfer (GET/POST /files) with gzip, multipart, chown, ENOSPC - Port subsystem (/proc/net/tcp scanner, socat forwarder) - Cgroup2 manager with noop fallback - Snapshot/restore lifecycle (conntracker, port subsystem stop/restart) - SIGTERM graceful shutdown, --cmd initial process spawn - MMDS metadata polling for Firecracker mode 42 source files, ~4200 LOC, 4.1MB stripped release binary. Makefile updated: build-envd now targets Rust (musl static), build-envd-go preserved for Go builds.
113 lines
2.8 KiB
Rust
113 lines
2.8 KiB
Rust
use std::io::{self, BufRead};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ConnStat {
|
|
pub local_ip: String,
|
|
pub local_port: u32,
|
|
pub status: String,
|
|
pub family: u32,
|
|
pub inode: u64,
|
|
}
|
|
|
|
fn tcp_state_name(hex: &str) -> &'static str {
|
|
match hex {
|
|
"01" => "ESTABLISHED",
|
|
"02" => "SYN_SENT",
|
|
"03" => "SYN_RECV",
|
|
"04" => "FIN_WAIT1",
|
|
"05" => "FIN_WAIT2",
|
|
"06" => "TIME_WAIT",
|
|
"07" => "CLOSE",
|
|
"08" => "CLOSE_WAIT",
|
|
"09" => "LAST_ACK",
|
|
"0A" => "LISTEN",
|
|
"0B" => "CLOSING",
|
|
_ => "UNKNOWN",
|
|
}
|
|
}
|
|
|
|
pub fn read_tcp_connections() -> Vec<ConnStat> {
|
|
let mut conns = Vec::new();
|
|
if let Ok(c) = parse_proc_net_tcp("/proc/net/tcp", libc::AF_INET as u32) {
|
|
conns.extend(c);
|
|
}
|
|
if let Ok(c) = parse_proc_net_tcp("/proc/net/tcp6", libc::AF_INET6 as u32) {
|
|
conns.extend(c);
|
|
}
|
|
conns
|
|
}
|
|
|
|
fn parse_proc_net_tcp(path: &str, family: u32) -> io::Result<Vec<ConnStat>> {
|
|
let file = std::fs::File::open(path)?;
|
|
let reader = io::BufReader::new(file);
|
|
let mut conns = Vec::new();
|
|
let mut first = true;
|
|
|
|
for line in reader.lines() {
|
|
let line = line?;
|
|
if first {
|
|
first = false;
|
|
continue;
|
|
}
|
|
let line = line.trim().to_string();
|
|
if line.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
let fields: Vec<&str> = line.split_whitespace().collect();
|
|
if fields.len() < 10 {
|
|
continue;
|
|
}
|
|
|
|
let (ip, port) = match parse_hex_addr(fields[1], family) {
|
|
Some(v) => v,
|
|
None => continue,
|
|
};
|
|
|
|
let state = tcp_state_name(fields[3]);
|
|
|
|
let inode: u64 = match fields[9].parse() {
|
|
Ok(v) => v,
|
|
Err(_) => continue,
|
|
};
|
|
|
|
conns.push(ConnStat {
|
|
local_ip: ip,
|
|
local_port: port,
|
|
status: state.to_string(),
|
|
family,
|
|
inode,
|
|
});
|
|
}
|
|
|
|
Ok(conns)
|
|
}
|
|
|
|
fn parse_hex_addr(s: &str, family: u32) -> Option<(String, u32)> {
|
|
let (ip_hex, port_hex) = s.split_once(':')?;
|
|
let port = u32::from_str_radix(port_hex, 16).ok()?;
|
|
let ip_bytes = hex::decode(ip_hex).ok()?;
|
|
|
|
let ip_str = if family == libc::AF_INET as u32 {
|
|
if ip_bytes.len() != 4 {
|
|
return None;
|
|
}
|
|
format!("{}.{}.{}.{}", ip_bytes[3], ip_bytes[2], ip_bytes[1], ip_bytes[0])
|
|
} else {
|
|
if ip_bytes.len() != 16 {
|
|
return None;
|
|
}
|
|
let mut octets = [0u8; 16];
|
|
for i in 0..4 {
|
|
octets[i * 4] = ip_bytes[i * 4 + 3];
|
|
octets[i * 4 + 1] = ip_bytes[i * 4 + 2];
|
|
octets[i * 4 + 2] = ip_bytes[i * 4 + 1];
|
|
octets[i * 4 + 3] = ip_bytes[i * 4];
|
|
}
|
|
let addr = std::net::Ipv6Addr::from(octets);
|
|
addr.to_string()
|
|
};
|
|
|
|
Some((ip_str, port))
|
|
}
|