neli/
connector.rs

1//! Connector module for Linux Netlink connector messages.
2//!
3//! This module provides support for the Linux Netlink connector subsystem,
4//! which creates a communication channel between userspace programs and the kernel.
5//! It allows applications to receive notifications about various kernel events.
6//!
7//! This module currently provides full support for the Linux proc connector protocol,
8//! enabling the reception and handling of process lifecycle events such as creation,
9//! termination, exec, UID/GID/sid changes, tracing, name changes, and core dumps.
10//!
11//! ## Supported protocols
12//! At this time, only the proc connector (`PROC_CN`) protocol is fully implemented.
13//!
14//! ## Extensibility
15//! The implementation can be extended in two ways:
16//! 1. By defining additional types and logic in your own crate and using them with this module.
17//! 2. By using a `Vec<u8>` as a payload and manually parsing protocol messages to suit other connector protocols.
18//!
19//! This design allows both high-level ergonomic handling of proc events and low-level manual parsing for custom needs.
20use crate::{
21    self as neli, FromBytesWithInput, Header, Size, ToBytes,
22    consts::connector::{CnMsgIdx, CnMsgVal, ProcEventType},
23    err::{DeError, MsgError},
24};
25use byteorder::{NativeEndian, ReadBytesExt};
26use derive_builder::Builder;
27use getset::Getters;
28use log::trace;
29use std::{io::Cursor, io::Read};
30
31/// Netlink connector message header and payload.
32#[derive(
33    Builder, Getters, Clone, Debug, PartialEq, Eq, Size, ToBytes, FromBytesWithInput, Header,
34)]
35#[neli(from_bytes_bound = "P: Size + FromBytesWithInput<Input = usize>")]
36#[builder(pattern = "owned")]
37pub struct CnMsg<P: Size> {
38    /// Index of the connector (idx)
39    #[getset(get = "pub")]
40    idx: CnMsgIdx,
41    /// Value (val)
42    #[getset(get = "pub")]
43    val: CnMsgVal,
44    /// Sequence number
45    #[builder(default)]
46    #[getset(get = "pub")]
47    seq: u32,
48    /// Acknowledgement number
49    #[builder(default)]
50    #[getset(get = "pub")]
51    ack: u32,
52    /// Length of the payload
53    #[builder(
54        setter(skip),
55        default = "self.payload.as_ref().unwrap().unpadded_size() as _"
56    )]
57    #[getset(get = "pub")]
58    len: u16,
59    /// Flags
60    #[builder(default)]
61    #[getset(get = "pub")]
62    flags: u16,
63    /// Payload of the netlink message
64    ///
65    /// You can either use predefined types like `ProcCnMcastOp` or `ProcEventHeader`,
66    /// a custom type defined by you or `Vec<u8>` for raw payload.
67    #[neli(size = "len as usize")]
68    #[neli(input = "(len as usize)")]
69    #[getset(get = "pub")]
70    pub(crate) payload: P,
71}
72
73// -- proc connector structs --
74
75/// Header for process event messages.
76#[derive(Debug, Size)]
77pub struct ProcEventHeader {
78    /// The CPU on which the event occurred.
79    pub cpu: u32,
80    /// Nanosecond timestamp of the event.
81    pub timestamp_ns: u64,
82    /// The process event data.
83    pub event: ProcEvent,
84}
85
86/// Ergonomic enum for process event data.
87#[derive(Debug, Size, Copy, Clone)]
88pub enum ProcEvent {
89    /// Acknowledgement event, typically for PROC_EVENT_NONE.
90    Ack {
91        /// Error code (0 for success).
92        err: u32,
93    },
94    /// Fork event, triggered when a process forks.
95    Fork {
96        /// Parent process PID.
97        parent_pid: i32,
98        /// Parent process TGID (thread group ID).
99        parent_tgid: i32,
100        /// Child process PID.
101        child_pid: i32,
102        /// Child process TGID.
103        child_tgid: i32,
104    },
105    /// Exec event, triggered when a process calls exec().
106    Exec {
107        /// Process PID.
108        process_pid: i32,
109        /// Process TGID.
110        process_tgid: i32,
111    },
112    /// UID change event, triggered when a process changes its UID.
113    Uid {
114        /// Process PID.
115        process_pid: i32,
116        /// Process TGID.
117        process_tgid: i32,
118        /// Real UID.
119        ruid: u32,
120        /// Effective UID.
121        euid: u32,
122    },
123    /// GID change event, triggered when a process changes its GID.
124    Gid {
125        /// Process PID.
126        process_pid: i32,
127        /// Process TGID.
128        process_tgid: i32,
129        /// Real GID.
130        rgid: u32,
131        /// Effective GID.
132        egid: u32,
133    },
134    /// SID change event, triggered when a process changes its session ID.
135    Sid {
136        /// Process PID.
137        process_pid: i32,
138        /// Process TGID.
139        process_tgid: i32,
140    },
141    /// Ptrace event, triggered when a process is traced.
142    Ptrace {
143        /// Process PID.
144        process_pid: i32,
145        /// Process TGID.
146        process_tgid: i32,
147        /// Tracer process PID.
148        tracer_pid: i32,
149        /// Tracer process TGID.
150        tracer_tgid: i32,
151    },
152    /// Comm event, triggered when a process changes its command name.
153    Comm {
154        /// Process PID.
155        process_pid: i32,
156        /// Process TGID.
157        process_tgid: i32,
158        /// Command name (null-terminated, max 16 bytes).
159        comm: [u8; 16],
160    },
161    /// Coredump event, triggered when a process dumps core.
162    Coredump {
163        /// Process PID.
164        process_pid: i32,
165        /// Process TGID.
166        process_tgid: i32,
167        /// Parent process PID.
168        parent_pid: i32,
169        /// Parent process TGID.
170        parent_tgid: i32,
171    },
172    /// Exit event, triggered when a process exits.
173    Exit {
174        /// Process PID.
175        process_pid: i32,
176        /// Process TGID.
177        process_tgid: i32,
178        /// Exit code.
179        exit_code: u32,
180        /// Exit signal.
181        exit_signal: u32,
182        /// Parent process PID.
183        parent_pid: i32,
184        /// Parent process TGID.
185        parent_tgid: i32,
186    },
187}
188
189impl FromBytesWithInput for ProcEventHeader {
190    type Input = usize;
191
192    fn from_bytes_with_input(
193        buffer: &mut Cursor<impl AsRef<[u8]>>,
194        input: Self::Input,
195    ) -> Result<Self, DeError> {
196        let start = buffer.position() as usize;
197        let bytes = buffer.get_ref().as_ref();
198
199        trace!("Parsing ProcEventHeader at position {start} with input size {input}");
200
201        // Minimum size for header (16) + smallest event (ack: 4) is 20.
202        if input < 16 || bytes.len() < start + input {
203            return Err(DeError::InvalidInput(input));
204        }
205
206        // Read header fields: what (u32), cpu (u32), timestamp_ns (u64)
207        let what_val = buffer.read_u32::<NativeEndian>()?;
208        let what = ProcEventType::from(what_val);
209        let cpu = buffer.read_u32::<NativeEndian>()?;
210        let timestamp_ns = buffer.read_u64::<NativeEndian>()?;
211
212        let event = match what {
213            ProcEventType::None => ProcEvent::Ack {
214                err: buffer.read_u32::<NativeEndian>()?,
215            },
216            ProcEventType::Fork => ProcEvent::Fork {
217                parent_pid: buffer.read_i32::<NativeEndian>()?,
218                parent_tgid: buffer.read_i32::<NativeEndian>()?,
219                child_pid: buffer.read_i32::<NativeEndian>()?,
220                child_tgid: buffer.read_i32::<NativeEndian>()?,
221            },
222            ProcEventType::Exec => ProcEvent::Exec {
223                process_pid: buffer.read_i32::<NativeEndian>()?,
224                process_tgid: buffer.read_i32::<NativeEndian>()?,
225            },
226            ProcEventType::Uid => ProcEvent::Uid {
227                process_pid: buffer.read_i32::<NativeEndian>()?,
228                process_tgid: buffer.read_i32::<NativeEndian>()?,
229                ruid: buffer.read_u32::<NativeEndian>()?,
230                euid: buffer.read_u32::<NativeEndian>()?,
231            },
232            ProcEventType::Gid => ProcEvent::Gid {
233                process_pid: buffer.read_i32::<NativeEndian>()?,
234                process_tgid: buffer.read_i32::<NativeEndian>()?,
235                rgid: buffer.read_u32::<NativeEndian>()?,
236                egid: buffer.read_u32::<NativeEndian>()?,
237            },
238            ProcEventType::Sid => ProcEvent::Sid {
239                process_pid: buffer.read_i32::<NativeEndian>()?,
240                process_tgid: buffer.read_i32::<NativeEndian>()?,
241            },
242            ProcEventType::Ptrace => ProcEvent::Ptrace {
243                process_pid: buffer.read_i32::<NativeEndian>()?,
244                process_tgid: buffer.read_i32::<NativeEndian>()?,
245                tracer_pid: buffer.read_i32::<NativeEndian>()?,
246                tracer_tgid: buffer.read_i32::<NativeEndian>()?,
247            },
248            ProcEventType::Comm => {
249                let process_pid = buffer.read_i32::<NativeEndian>()?;
250                let process_tgid = buffer.read_i32::<NativeEndian>()?;
251                let mut comm = [0u8; 16];
252                buffer.read_exact(&mut comm)?;
253                ProcEvent::Comm {
254                    process_pid,
255                    process_tgid,
256                    comm,
257                }
258            }
259            ProcEventType::Coredump => ProcEvent::Coredump {
260                process_pid: buffer.read_i32::<NativeEndian>()?,
261                process_tgid: buffer.read_i32::<NativeEndian>()?,
262                parent_pid: buffer.read_i32::<NativeEndian>()?,
263                parent_tgid: buffer.read_i32::<NativeEndian>()?,
264            },
265            ProcEventType::Exit | ProcEventType::NonzeroExit => ProcEvent::Exit {
266                process_pid: buffer.read_i32::<NativeEndian>()?,
267                process_tgid: buffer.read_i32::<NativeEndian>()?,
268                exit_code: buffer.read_u32::<NativeEndian>()?,
269                exit_signal: buffer.read_u32::<NativeEndian>()?,
270                parent_pid: buffer.read_i32::<NativeEndian>()?,
271                parent_tgid: buffer.read_i32::<NativeEndian>()?,
272            },
273            ProcEventType::UnrecognizedConst(i) => {
274                return Err(DeError::Msg(MsgError::new(format!(
275                    "Unrecognized Proc event type: {i} (raw value: {what_val})"
276                ))));
277            }
278        };
279
280        // consume the entire len, because the kernel can pad the event data with zeros
281        buffer.set_position(start as u64 + input as u64);
282
283        Ok(ProcEventHeader {
284            cpu,
285            timestamp_ns,
286            event,
287        })
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    static RAW_RESPONSE: &[u8] = &[
296        1, 0, 0, 0, 1, 0, 0, 0, 122, 2, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 184,
297        52, 84, 25, 71, 2, 0, 0, 127, 22, 0, 0, 127, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
298        0, 0, 0, 0,
299    ];
300
301    #[test]
302    fn parse_static_proc_header() {
303        let mut cursor = Cursor::new(&RAW_RESPONSE);
304
305        let msg: CnMsg<ProcEventHeader> =
306            CnMsg::from_bytes_with_input(&mut cursor, RAW_RESPONSE.len()).unwrap();
307
308        assert_eq!(msg.idx(), &CnMsgIdx::Proc);
309        assert_eq!(msg.val(), &CnMsgVal::Proc);
310        assert_eq!(msg.payload.cpu, 1);
311        assert_eq!(msg.payload.timestamp_ns, 2504390882488);
312        match &msg.payload.event {
313            ProcEvent::Exec {
314                process_pid,
315                process_tgid,
316            } => {
317                assert_eq!(*process_pid, 5759);
318                assert_eq!(*process_tgid, 5759);
319            }
320            _ => panic!("Expected Exec event"),
321        }
322    }
323
324    #[test]
325    fn parse_static_raw_data() {
326        let mut cursor = Cursor::new(&RAW_RESPONSE);
327
328        let msg: CnMsg<Vec<u8>> =
329            CnMsg::from_bytes_with_input(&mut cursor, RAW_RESPONSE.len()).unwrap();
330
331        assert_eq!(msg.idx(), &CnMsgIdx::Proc);
332        assert_eq!(msg.val(), &CnMsgVal::Proc);
333        assert_eq!(msg.payload, RAW_RESPONSE[CnMsg::<Vec<u8>>::header_size()..]);
334    }
335}