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    let payload = &payload[..len];
78
79    if payload.len() < SIZE_NLMSGHDR {
80        return Err(Error::Truncated);
81    }
82
83    let nlmsg_type = read_u16(payload, 4);
84    let nlmsg_len = read_u32(payload, 0) as usize;
85
86    if nlmsg_len > payload.len() {
87        return Err(Error::Truncated);
88    }
89
90    match nlmsg_type {
91        NLMSG_NOOP => Ok(None),
92        NLMSG_DONE if nlmsg_len == SIZE_NLMSGHDR => Ok(None),
93        NLMSG_ERROR => {
94            // NLMSG_ERROR payload is struct nlmsgerr { int error; struct nlmsghdr msg; }
95            let errno = read_i32(payload, SIZE_NLMSGHDR);
96            if errno == 0 {
97                // ACK (error == 0 means success), ignore
98                return Ok(None);
99            }
100            // Kernel stores errno as negative (e.g. -EPERM = -1).
101            // std::io::Error::from_raw_os_error expects positive values.
102            let pos_errno = errno.checked_neg().unwrap_or(errno);
103            Err(Error::Os(std::io::Error::from_raw_os_error(pos_errno)))
104        }
105        NLMSG_OVERRUN => Err(Error::Overrun),
106        _ => {
107            // Normal data message: parse cn_msg + proc_event.
108            // Payload starts after nlmsghdr.
109            let cn_offset = nlmsg_hdrlen();
110            if nlmsg_len < cn_offset {
111                return Err(Error::Truncated);
112            }
113            let cn_payload = &payload[cn_offset..nlmsg_len];
114            parse_cn_msg(cn_payload).map(Some)
115        }
116    }
117}
118
119/// Parse a `cn_msg` payload (starting from `cb_id`) into a `ProcEvent`.
120///
121/// # Example
122///
123/// ```
124/// use proc_connector::{parse_cn_msg, Error};
125///
126/// // Too short → Truncated
127/// let buf = [0u8; 10];
128/// assert!(matches!(parse_cn_msg(&buf), Err(Error::Truncated)));
129///
130/// // Wrong connector index → UnexpectedConnector
131/// let mut buf = [0u8; 20];
132/// buf[0..4].copy_from_slice(&999u32.to_ne_bytes()); // wrong idx
133/// assert!(matches!(parse_cn_msg(&buf), Err(Error::UnexpectedConnector)));
134/// ```
135pub fn parse_cn_msg(buf: &[u8]) -> Result<ProcEvent> {
136    if buf.len() < SIZE_CN_MSG {
137        return Err(Error::Truncated);
138    }
139
140    let idx = read_u32(buf, 0);
141    let val = read_u32(buf, 4);
142
143    // Only handle proc events
144    if idx != CN_IDX_PROC || val != CN_VAL_PROC {
145        return Err(Error::UnexpectedConnector);
146    }
147
148    let data_len = read_u16(buf, 16) as usize;
149
150    // proc_event data starts at offset 20 (after fixed cn_msg header)
151    let proc_off = SIZE_CN_MSG;
152    let proc_data = if buf.len() >= proc_off + data_len {
153        &buf[proc_off..proc_off + data_len]
154    } else {
155        return Err(Error::Truncated);
156    };
157
158    parse_proc_event(proc_data)
159}
160
161/// Parse a `proc_event` struct into a `ProcEvent` enum.
162fn parse_proc_event(buf: &[u8]) -> Result<ProcEvent> {
163    if buf.len() < PROC_EVENT_HEADER_SIZE {
164        return Err(Error::Truncated);
165    }
166
167    let what = read_u32(buf, 0);
168    let _cpu = read_u32(buf, 4);
169    let timestamp_ns = read_u64(buf, 8);
170
171    let data = &buf[PROC_EVENT_HEADER_SIZE..];
172
173    match what {
174        PROC_EVENT_EXEC => {
175            if data.len() < SIZE_EXEC_EVENT {
176                return Err(Error::Truncated);
177            }
178            Ok(ProcEvent::Exec {
179                pid: read_i32(data, EXEC_PID) as u32,
180                tgid: read_i32(data, EXEC_TGID) as u32,
181                timestamp_ns,
182            })
183        }
184
185        PROC_EVENT_FORK => {
186            if data.len() < SIZE_FORK_EVENT {
187                return Err(Error::Truncated);
188            }
189            Ok(ProcEvent::Fork {
190                parent_pid: read_i32(data, FORK_PARENT_PID) as u32,
191                parent_tgid: read_i32(data, FORK_PARENT_TGID) as u32,
192                child_pid: read_i32(data, FORK_CHILD_PID) as u32,
193                child_tgid: read_i32(data, FORK_CHILD_TGID) as u32,
194                timestamp_ns,
195            })
196        }
197
198        PROC_EVENT_EXIT => {
199            if data.len() < SIZE_EXIT_EVENT {
200                return Err(Error::Truncated);
201            }
202            Ok(ProcEvent::Exit {
203                pid: read_i32(data, EXIT_PID) as u32,
204                tgid: read_i32(data, EXIT_TGID) as u32,
205                exit_code: read_u32(data, EXIT_CODE),
206                exit_signal: read_u32(data, EXIT_SIGNAL),
207                timestamp_ns,
208            })
209        }
210
211        PROC_EVENT_UID => {
212            if data.len() < SIZE_ID_EVENT {
213                return Err(Error::Truncated);
214            }
215            Ok(ProcEvent::Uid {
216                pid: read_i32(data, ID_PID) as u32,
217                tgid: read_i32(data, ID_TGID) as u32,
218                ruid: read_u32(data, ID_RUID_RGID),
219                euid: read_u32(data, ID_EUID_EGID),
220                timestamp_ns,
221            })
222        }
223
224        PROC_EVENT_GID => {
225            if data.len() < SIZE_ID_EVENT {
226                return Err(Error::Truncated);
227            }
228            Ok(ProcEvent::Gid {
229                pid: read_i32(data, ID_PID) as u32,
230                tgid: read_i32(data, ID_TGID) as u32,
231                rgid: read_u32(data, ID_RUID_RGID),
232                egid: read_u32(data, ID_EUID_EGID),
233                timestamp_ns,
234            })
235        }
236
237        PROC_EVENT_SID => {
238            if data.len() < SIZE_SID_EVENT {
239                return Err(Error::Truncated);
240            }
241            Ok(ProcEvent::Sid {
242                pid: read_i32(data, SID_PID) as u32,
243                tgid: read_i32(data, SID_TGID) as u32,
244                timestamp_ns,
245            })
246        }
247
248        PROC_EVENT_PTRACE => {
249            if data.len() < SIZE_PTRACE_EVENT {
250                return Err(Error::Truncated);
251            }
252            Ok(ProcEvent::Ptrace {
253                pid: read_i32(data, PTRACE_PID) as u32,
254                tgid: read_i32(data, PTRACE_TGID) as u32,
255                tracer_pid: read_i32(data, PTRACE_TRACER_PID) as u32,
256                tracer_tgid: read_i32(data, PTRACE_TRACER_TGID) as u32,
257                timestamp_ns,
258            })
259        }
260
261        PROC_EVENT_COMM => {
262            if data.len() < SIZE_COMM_EVENT {
263                return Err(Error::Truncated);
264            }
265            let mut comm = [0u8; 16];
266            comm.copy_from_slice(&data[COMM_DATA..COMM_DATA + 16]);
267            Ok(ProcEvent::Comm {
268                pid: read_i32(data, COMM_PID) as u32,
269                tgid: read_i32(data, COMM_TGID) as u32,
270                comm,
271                timestamp_ns,
272            })
273        }
274
275        PROC_EVENT_COREDUMP => {
276            if data.len() < SIZE_COREDUMP_EVENT {
277                return Err(Error::Truncated);
278            }
279            Ok(ProcEvent::Coredump {
280                pid: read_i32(data, COREDUMP_PID) as u32,
281                tgid: read_i32(data, COREDUMP_TGID) as u32,
282                timestamp_ns,
283            })
284        }
285
286        _ => {
287            // Unknown event — forward compatibility
288            Ok(ProcEvent::Unknown {
289                what,
290                raw_data: data.to_vec(),
291            })
292        }
293    }
294}
295
296// ---------------------------------------------------------------------------
297// Convenience: find first event from a buffer
298// ---------------------------------------------------------------------------
299
300/// Find the first real event from a buffer.
301pub fn first_event_from_buf(buf: &[u8], n: usize) -> Result<Option<ProcEvent>> {
302    let iter = crate::iter::NetlinkMessageIter::new(buf, n);
303    for msg in iter {
304        match msg? {
305            Some(event) => return Ok(Some(event)),
306            None => continue,
307        }
308    }
309    Ok(None)
310}