use chrono::{DateTime, TimeZone, Utc};
#[derive(Debug, Clone)]
pub enum EbpfEvent {
Connect(ConnectEvent),
}
#[derive(Debug, Clone)]
pub struct ConnectEvent {
pub tgid: u32,
pub pid: u32,
pub comm: String,
pub saddr: std::net::Ipv4Addr,
pub daddr: std::net::Ipv4Addr,
pub sport: u16,
pub dport: u16,
pub timestamp: DateTime<Utc>,
}
impl ConnectEvent {
pub(crate) fn decode(
raw: &crate::wire::ConnectV4Event,
boot_time: DateTime<Utc>,
) -> Self {
let comm_end = raw
.comm
.iter()
.position(|&b| b == 0)
.unwrap_or(raw.comm.len());
let comm = String::from_utf8_lossy(&raw.comm[..comm_end]).into_owned();
let saddr = std::net::Ipv4Addr::from(raw.saddr.to_ne_bytes());
let daddr = std::net::Ipv4Addr::from(raw.daddr.to_ne_bytes());
let sport = u16::from_be_bytes(raw.sport.to_ne_bytes());
let dport = u16::from_be_bytes(raw.dport.to_ne_bytes());
let timestamp = boot_time + chrono::Duration::nanoseconds(raw.timestamp_ns as i64);
Self {
tgid: raw.tgid,
pid: raw.pid,
comm,
saddr,
daddr,
sport,
dport,
timestamp,
}
}
}
#[allow(dead_code)]
pub(crate) fn estimate_boot_time() -> DateTime<Utc> {
#[cfg(target_os = "linux")]
{
if let Ok(uptime_str) = std::fs::read_to_string("/proc/uptime") {
if let Some(secs_str) = uptime_str.split_whitespace().next() {
if let Ok(secs) = secs_str.parse::<f64>() {
let now = Utc::now();
let boot_secs = secs as i64;
let boot_nanos = ((secs.fract()) * 1e9) as i64;
return now
- chrono::Duration::seconds(boot_secs)
- chrono::Duration::nanoseconds(boot_nanos);
}
}
}
}
Utc.timestamp_opt(0, 0).single().unwrap_or_else(Utc::now)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wire::{ConnectV4Event, EventKind, COMM_LEN};
#[test]
fn decode_converts_addresses_and_ports_to_host_order() {
let mut comm = [0u8; COMM_LEN];
comm[..4].copy_from_slice(b"curl");
let raw = ConnectV4Event {
kind: EventKind::TcpV4Connect,
_pad0: [0; 3],
tgid: 1234,
pid: 1235,
saddr: u32::from_ne_bytes([192, 168, 1, 10]),
daddr: u32::from_ne_bytes([1, 1, 1, 1]),
sport: 0,
dport: u16::from_ne_bytes([0x01, 0xBB]),
comm,
timestamp_ns: 1_000_000_000,
};
let boot = Utc.timestamp_opt(1_700_000_000, 0).unwrap();
let ev = ConnectEvent::decode(&raw, boot);
assert_eq!(ev.pid, 1235);
assert_eq!(ev.tgid, 1234);
assert_eq!(ev.comm, "curl");
assert_eq!(ev.saddr, std::net::Ipv4Addr::new(192, 168, 1, 10));
assert_eq!(ev.daddr, std::net::Ipv4Addr::new(1, 1, 1, 1));
assert_eq!(ev.dport, 443);
assert_eq!(ev.timestamp.timestamp(), 1_700_000_001);
}
#[test]
fn decode_handles_short_comm_without_panicking() {
let raw = ConnectV4Event::empty();
let ev = ConnectEvent::decode(&raw, Utc::now());
assert_eq!(ev.comm, "");
}
}