Skip to main content

planarian_backend_netwatch/
ebpf.rs

1//! The event the eBPF program reports (`crates/backends/netwatch-ebpf`,
2//! `Event`): `#[repr(C)] { cgroup: u64, tgid: u32, hook: u32, len: u32,
3//! captured: u32, netns: u32, cgflags: u32, cgpath: [[u8; NAME]; LEVELS],
4//! data: [u8; CAPTURE] }`, native-endian. Parsed by hand: no
5//! unsafe, and a short buffer is an error rather than a misread.
6
7pub const CAPTURE: usize = 512;
8pub const LEVELS: usize = 4;
9pub const NAME: usize = 96;
10/// In `cgflags`: the probe's walk reached the root, so `cgpath` is whole.
11pub const CG_ROOT: u32 = 1 << 31;
12const HEADER: usize = 8 + 4 * 6;
13pub const EVENT_SIZE: usize = HEADER + LEVELS * NAME + CAPTURE;
14
15pub const HOOK_RTNETLINK: u32 = 1;
16pub const HOOK_NFNETLINK: u32 = 2;
17pub const HOOK_GENL: u32 = 3;
18/// `nfnetlink_rcv_batch`: an nftables batch (its header is the batch's).
19pub const HOOK_NFBATCH: u32 = 4;
20/// `nf_setsockopt`: a legacy table change; `data` is `pf` then the option.
21pub const HOOK_SOCKOPT: u32 = 5;
22/// `register_netdevice`: an interface registered, of any kind; `data` is
23/// its name, `netns` its own.
24pub const HOOK_NETDEV: u32 = 6;
25
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct Event {
28    pub cgroup: u64,
29    pub tgid: u32,
30    pub hook: u32,
31    /// The message's own length.
32    pub len: u32,
33    /// The inode of the network namespace the request acts on; 0 if the
34    /// probe could not read it.
35    pub netns: u32,
36    /// The sender's cgroup path as the kernel had it at the request
37    /// (`system.slice/foo.service`), when the probe read all of it.
38    pub cgpath: Option<String>,
39    /// The captured bytes of it (`captured` long).
40    pub data: Vec<u8>,
41}
42
43impl Event {
44    pub fn parse(b: &[u8]) -> Option<Event> {
45        if b.len() < EVENT_SIZE {
46            return None;
47        }
48        let u32_at = |o: usize| u32::from_ne_bytes(b[o..o + 4].try_into().unwrap());
49        let captured = (u32_at(20) as usize).min(CAPTURE);
50        Some(Event {
51            cgroup: u64::from_ne_bytes(b[0..8].try_into().unwrap()),
52            tgid: u32_at(8),
53            hook: u32_at(12),
54            len: u32_at(16),
55            netns: u32_at(24),
56            cgpath: cgpath(u32_at(28), &b[HEADER..HEADER + LEVELS * NAME]),
57            data: b[HEADER + LEVELS * NAME..HEADER + LEVELS * NAME + captured].to_vec(),
58        })
59    }
60
61    /// Whether the message was longer than what was captured.
62    pub fn truncated(&self) -> bool {
63        self.len as usize > self.data.len()
64    }
65
66    /// `nlmsg_type`, from the captured header.
67    pub fn nlmsg_type(&self) -> Option<u16> {
68        self.data.get(4..6).map(|b| u16::from_ne_bytes(b.try_into().unwrap()))
69    }
70}
71
72/// The whole path from the probe's leaf-first names, or `None` if the walk
73/// did not reach the root or a name filled its slot (may be cut short).
74fn cgpath(flags: u32, names: &[u8]) -> Option<String> {
75    let n = (flags & !CG_ROOT) as usize;
76    if flags & CG_ROOT == 0 || n == 0 || n > LEVELS {
77        return None;
78    }
79    let mut parts = Vec::with_capacity(n);
80    for slot in names.chunks(NAME).take(n) {
81        let end = slot.iter().position(|&c| c == 0)?;
82        if end == 0 {
83            return None;
84        }
85        parts.push(std::str::from_utf8(&slot[..end]).ok()?);
86    }
87    parts.reverse();
88    Some(parts.join("/"))
89}