Skip to main content

proc_connector/
parse.rs

1//! Netlink message parsing for process events.
2//!
3//! This module contains the parsing logic for netlink messages,
4//! including `parse_netlink_message`, `parse_cn_msg`, and `parse_proc_event`.
5
6use crate::consts::*;
7use crate::error::{Error, Result};
8use crate::proc_event::ProcEvent;
9
10// ---------------------------------------------------------------------------
11// Wire format helpers (private)
12// ---------------------------------------------------------------------------
13
14/// Read a `u32` from a byte slice at a given offset (native endian).
15#[inline]
16fn read_u32(buf: &[u8], off: usize) -> u32 {
17    let arr: [u8; 4] = buf[off..off + 4].try_into().unwrap();
18    u32::from_ne_bytes(arr)
19}
20
21/// Read a `u16` from a byte slice at a given offset (native endian).
22#[inline]
23fn read_u16(buf: &[u8], off: usize) -> u16 {
24    let arr: [u8; 2] = buf[off..off + 2].try_into().unwrap();
25    u16::from_ne_bytes(arr)
26}
27
28/// Read an `i32` from a byte slice at a given offset (native endian).
29#[inline]
30fn read_i32(buf: &[u8], off: usize) -> i32 {
31    let arr: [u8; 4] = buf[off..off + 4].try_into().unwrap();
32    i32::from_ne_bytes(arr)
33}
34
35/// Read a `u64` from a byte slice at a given offset (native endian).
36#[inline]
37fn read_u64(buf: &[u8], off: usize) -> u64 {
38    let arr: [u8; 8] = buf[off..off + 8].try_into().unwrap();
39    u64::from_ne_bytes(arr)
40}
41
42// ---------------------------------------------------------------------------
43// Parsing entry point
44// ---------------------------------------------------------------------------
45
46/// Parse a single netlink message payload (starting after `nlmsghdr`) into
47/// a `ProcEvent`.
48///
49/// `payload` is the full received buffer starting at the `nlmsghdr`.
50/// `len` is the number of valid bytes in `payload`.
51///
52/// This function handles:
53/// - `NLMSG_NOOP` → `None` (caller should continue reading)
54/// - `NLMSG_DONE` (with no payload, i.e., true multi-part terminator) → `None`
55///   Note: the kernel connector protocol uses `NLMSG_DONE` with a payload for all
56///   data messages, so only 16-byte `NLMSG_DONE` is treated as a control message.
57/// - `NLMSG_ERROR` → `Err`
58/// - `NLMSG_OVERRUN` → `Err(Overrun)`
59/// - `NLMSG_DATA` + valid `cn_msg` + `proc_event` → `Some(ProcEvent)`
60///
61/// # Example
62///
63/// ```
64/// use proc_connector::{parse_netlink_message, Error};
65///
66/// // Too short → Truncated
67/// let buf = [0u8; 4];
68/// assert!(matches!(parse_netlink_message(&buf, 4), Err(Error::Truncated)));
69///
70/// // NLMSG_NOOP → None
71/// let mut buf = [0u8; 16];
72/// buf[0..4].copy_from_slice(&16u32.to_ne_bytes()); // nlmsg_len
73/// buf[4..6].copy_from_slice(&1u16.to_ne_bytes());   // nlmsg_type = NLMSG_NOOP
74/// assert!(parse_netlink_message(&buf, 16).unwrap().is_none());
75/// ```
76pub fn parse_netlink_message(payload: &[u8], len: usize) -> Result<Option<ProcEvent>> {
77    if len > payload.len() {
78        return Err(Error::Truncated);
79    }
80    let payload = &payload[..len];
81
82    if payload.len() < SIZE_NLMSGHDR {
83        return Err(Error::Truncated);
84    }
85
86    let nlmsg_type = read_u16(payload, 4);
87    let nlmsg_len = read_u32(payload, 0) as usize;
88
89    if nlmsg_len > payload.len() {
90        return Err(Error::Truncated);
91    }
92
93    match nlmsg_type {
94        NLMSG_NOOP => Ok(None),
95        NLMSG_DONE if nlmsg_len == SIZE_NLMSGHDR => Ok(None),
96        NLMSG_ERROR => {
97            if payload.len() < SIZE_NLMSGERR {
98                return Err(Error::Truncated);
99            }
100            let errno = read_i32(payload, SIZE_NLMSGHDR);
101            if errno == 0 {
102                // ACK (error == 0 means success), ignore
103                return Ok(None);
104            }
105            // Kernel stores errno as negative (e.g. -EPERM = -1).
106            // std::io::Error::from_raw_os_error expects positive values.
107            let pos_errno = errno.checked_neg().unwrap_or(errno);
108            Err(Error::Os(std::io::Error::from_raw_os_error(pos_errno)))
109        }
110        NLMSG_OVERRUN => Err(Error::Overrun),
111        _ => {
112            // Normal data message: parse cn_msg + proc_event.
113            // Payload starts after nlmsghdr.
114            let cn_offset = nlmsg_hdrlen();
115            if nlmsg_len < cn_offset {
116                return Err(Error::Truncated);
117            }
118            let cn_payload = &payload[cn_offset..nlmsg_len];
119            parse_cn_msg(cn_payload).map(Some)
120        }
121    }
122}
123
124/// Parse a `cn_msg` payload (starting from `cb_id`) into a `ProcEvent`.
125///
126/// # Example
127///
128/// ```
129/// use proc_connector::{parse_cn_msg, Error};
130///
131/// // Too short → Truncated
132/// let buf = [0u8; 10];
133/// assert!(matches!(parse_cn_msg(&buf), Err(Error::Truncated)));
134///
135/// // Wrong connector index → UnexpectedConnector
136/// let mut buf = [0u8; 20];
137/// buf[0..4].copy_from_slice(&999u32.to_ne_bytes()); // wrong idx
138/// assert!(matches!(parse_cn_msg(&buf), Err(Error::UnexpectedConnector)));
139/// ```
140pub fn parse_cn_msg(buf: &[u8]) -> Result<ProcEvent> {
141    if buf.len() < SIZE_CN_MSG {
142        return Err(Error::Truncated);
143    }
144
145    let idx = read_u32(buf, 0);
146    let val = read_u32(buf, 4);
147
148    // Only handle proc events
149    if idx != CN_IDX_PROC || val != CN_VAL_PROC {
150        return Err(Error::UnexpectedConnector);
151    }
152
153    let data_len = read_u16(buf, 16) as usize;
154
155    // proc_event data starts at offset 20 (after fixed cn_msg header)
156    let proc_off = SIZE_CN_MSG;
157    let proc_data = if buf.len() >= proc_off + data_len {
158        &buf[proc_off..proc_off + data_len]
159    } else {
160        return Err(Error::Truncated);
161    };
162
163    parse_proc_event(proc_data)
164}
165
166/// Parse a `proc_event` struct into a `ProcEvent` enum.
167fn parse_proc_event(buf: &[u8]) -> Result<ProcEvent> {
168    if buf.len() < PROC_EVENT_HEADER_SIZE {
169        return Err(Error::Truncated);
170    }
171
172    let what = read_u32(buf, 0);
173    let cpu = read_u32(buf, 4);
174    let timestamp_ns = read_u64(buf, 8);
175    let data = &buf[PROC_EVENT_HEADER_SIZE..];
176
177    build_proc_event(what, cpu, timestamp_ns, data)
178}
179
180fn build_proc_event(what: u32, cpu: u32, timestamp_ns: u64, data: &[u8]) -> Result<ProcEvent> {
181    match what {
182        PROC_EVENT_EXEC => {
183            if data.len() < SIZE_EXEC_EVENT {
184                return Err(Error::Truncated);
185            }
186            Ok(ProcEvent::Exec {
187                cpu,
188                pid: read_i32(data, EXEC_PID) as u32,
189                tgid: read_i32(data, EXEC_TGID) as u32,
190                timestamp_ns,
191            })
192        }
193
194        PROC_EVENT_FORK => {
195            if data.len() < SIZE_FORK_EVENT {
196                return Err(Error::Truncated);
197            }
198            Ok(ProcEvent::Fork {
199                cpu,
200                parent_pid: read_i32(data, FORK_PARENT_PID) as u32,
201                parent_tgid: read_i32(data, FORK_PARENT_TGID) as u32,
202                child_pid: read_i32(data, FORK_CHILD_PID) as u32,
203                child_tgid: read_i32(data, FORK_CHILD_TGID) as u32,
204                timestamp_ns,
205            })
206        }
207
208        PROC_EVENT_EXIT => {
209            if data.len() < SIZE_EXIT_EVENT {
210                return Err(Error::Truncated);
211            }
212            Ok(ProcEvent::Exit {
213                cpu,
214                pid: read_i32(data, EXIT_PID) as u32,
215                tgid: read_i32(data, EXIT_TGID) as u32,
216                exit_code: read_u32(data, EXIT_CODE),
217                exit_signal: read_u32(data, EXIT_SIGNAL),
218                parent_pid: read_i32(data, EXIT_PARENT_PID) as u32,
219                parent_tgid: read_i32(data, EXIT_PARENT_TGID) as u32,
220                timestamp_ns,
221            })
222        }
223
224        PROC_EVENT_UID => {
225            if data.len() < SIZE_ID_EVENT {
226                return Err(Error::Truncated);
227            }
228            Ok(ProcEvent::Uid {
229                cpu,
230                pid: read_i32(data, ID_PID) as u32,
231                tgid: read_i32(data, ID_TGID) as u32,
232                ruid: read_u32(data, ID_RUID_RGID),
233                euid: read_u32(data, ID_EUID_EGID),
234                timestamp_ns,
235            })
236        }
237
238        PROC_EVENT_GID => {
239            if data.len() < SIZE_ID_EVENT {
240                return Err(Error::Truncated);
241            }
242            Ok(ProcEvent::Gid {
243                cpu,
244                pid: read_i32(data, ID_PID) as u32,
245                tgid: read_i32(data, ID_TGID) as u32,
246                rgid: read_u32(data, ID_RUID_RGID),
247                egid: read_u32(data, ID_EUID_EGID),
248                timestamp_ns,
249            })
250        }
251
252        PROC_EVENT_SID => {
253            if data.len() < SIZE_SID_EVENT {
254                return Err(Error::Truncated);
255            }
256            Ok(ProcEvent::Sid {
257                cpu,
258                pid: read_i32(data, SID_PID) as u32,
259                tgid: read_i32(data, SID_TGID) as u32,
260                timestamp_ns,
261            })
262        }
263
264        PROC_EVENT_PTRACE => {
265            if data.len() < SIZE_PTRACE_EVENT {
266                return Err(Error::Truncated);
267            }
268            Ok(ProcEvent::Ptrace {
269                cpu,
270                pid: read_i32(data, PTRACE_PID) as u32,
271                tgid: read_i32(data, PTRACE_TGID) as u32,
272                tracer_pid: read_i32(data, PTRACE_TRACER_PID) as u32,
273                tracer_tgid: read_i32(data, PTRACE_TRACER_TGID) as u32,
274                timestamp_ns,
275            })
276        }
277
278        PROC_EVENT_COMM => {
279            if data.len() < SIZE_COMM_EVENT {
280                return Err(Error::Truncated);
281            }
282            let mut comm = [0u8; 16];
283            comm.copy_from_slice(&data[COMM_DATA..COMM_DATA + 16]);
284            Ok(ProcEvent::Comm {
285                cpu,
286                pid: read_i32(data, COMM_PID) as u32,
287                tgid: read_i32(data, COMM_TGID) as u32,
288                comm,
289                timestamp_ns,
290            })
291        }
292
293        PROC_EVENT_COREDUMP => {
294            if data.len() < SIZE_COREDUMP_EVENT {
295                return Err(Error::Truncated);
296            }
297            Ok(ProcEvent::Coredump {
298                cpu,
299                pid: read_i32(data, COREDUMP_PID) as u32,
300                tgid: read_i32(data, COREDUMP_TGID) as u32,
301                parent_pid: read_i32(data, COREDUMP_PARENT_PID) as u32,
302                parent_tgid: read_i32(data, COREDUMP_PARENT_TGID) as u32,
303                timestamp_ns,
304            })
305        }
306
307        _ => Ok(ProcEvent::Unknown {
308            what,
309            raw_data: data.to_vec(),
310        }),
311    }
312}
313
314// ---------------------------------------------------------------------------
315// Convenience: find first event from a buffer
316// ---------------------------------------------------------------------------
317
318/// Find the first real event from a buffer.
319pub fn first_event_from_buf(buf: &[u8], n: usize) -> Result<Option<ProcEvent>> {
320    let iter = crate::iter::NetlinkMessageIter::new(buf, n);
321    for msg in iter {
322        match msg? {
323            Some(event) => return Ok(Some(event)),
324            None => continue,
325        }
326    }
327    Ok(None)
328}