Skip to main content

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.
20
21use std::{io::Cursor, io::Read};
22
23use derive_builder::{Builder, UninitializedFieldError};
24use getset::Getters;
25use log::trace;
26
27use crate::{
28    self as neli,
29    consts::connector::{CnMsgIdx, CnMsgVal, ProcEventType},
30    err::{DeError, MsgError, SerError},
31    FromBytes, FromBytesWithInput, Header, Size, ToBytes,
32};
33
34/// Netlink connector message header and payload.
35#[derive(
36    Builder, Getters, Clone, Debug, PartialEq, Eq, Size, ToBytes, FromBytesWithInput, Header,
37)]
38#[neli(from_bytes_bound = "P: Size + FromBytesWithInput<Input = usize>")]
39#[builder(pattern = "owned")]
40pub struct CnMsg<P: Size> {
41    /// Index of the connector (idx)
42    #[getset(get = "pub")]
43    idx: CnMsgIdx,
44    /// Value (val)
45    #[getset(get = "pub")]
46    val: CnMsgVal,
47    /// Sequence number
48    #[builder(default)]
49    #[getset(get = "pub")]
50    seq: u32,
51    /// Acknowledgement number
52    #[builder(default)]
53    #[getset(get = "pub")]
54    ack: u32,
55    /// Length of the payload
56    #[builder(
57        setter(skip),
58        default = "self.payload.as_ref().ok_or_else(|| UninitializedFieldError::new(\"payload\"))?.unpadded_size() as _"
59    )]
60    #[getset(get = "pub")]
61    len: u16,
62    /// Flags
63    #[builder(default)]
64    #[getset(get = "pub")]
65    flags: u16,
66    /// Payload of the netlink message
67    ///
68    /// You can either use predefined types like `ProcCnMcastOp` or `ProcEventHeader`,
69    /// a custom type defined by you or `Vec<u8>` for raw payload.
70    #[neli(size = "len as usize")]
71    #[neli(input = "input - Self::header_size()")]
72    #[getset(get = "pub")]
73    pub(crate) payload: P,
74}
75
76// -- proc connector structs --
77
78/// Header for process event messages.
79#[derive(Debug, Size)]
80pub struct ProcEventHeader {
81    /// The CPU on which the event occurred.
82    pub cpu: u32,
83    /// Nanosecond timestamp of the event.
84    pub timestamp_ns: u64,
85    /// The process event data.
86    pub event: ProcEvent,
87}
88
89/// Ergonomic enum for process event data.
90#[derive(Debug, Size, Copy, Clone)]
91pub enum ProcEvent {
92    /// Acknowledgement event, typically for PROC_EVENT_NONE.
93    Ack {
94        /// Error code (0 for success).
95        err: u32,
96    },
97    /// Fork event, triggered when a process forks.
98    Fork {
99        /// Parent process PID.
100        parent_pid: i32,
101        /// Parent process TGID (thread group ID).
102        parent_tgid: i32,
103        /// Child process PID.
104        child_pid: i32,
105        /// Child process TGID.
106        child_tgid: i32,
107    },
108    /// Exec event, triggered when a process calls exec().
109    Exec {
110        /// Process PID.
111        process_pid: i32,
112        /// Process TGID.
113        process_tgid: i32,
114    },
115    /// UID change event, triggered when a process changes its UID.
116    Uid {
117        /// Process PID.
118        process_pid: i32,
119        /// Process TGID.
120        process_tgid: i32,
121        /// Real UID.
122        ruid: u32,
123        /// Effective UID.
124        euid: u32,
125    },
126    /// GID change event, triggered when a process changes its GID.
127    Gid {
128        /// Process PID.
129        process_pid: i32,
130        /// Process TGID.
131        process_tgid: i32,
132        /// Real GID.
133        rgid: u32,
134        /// Effective GID.
135        egid: u32,
136    },
137    /// SID change event, triggered when a process changes its session ID.
138    Sid {
139        /// Process PID.
140        process_pid: i32,
141        /// Process TGID.
142        process_tgid: i32,
143    },
144    /// Ptrace event, triggered when a process is traced.
145    Ptrace {
146        /// Process PID.
147        process_pid: i32,
148        /// Process TGID.
149        process_tgid: i32,
150        /// Tracer process PID.
151        tracer_pid: i32,
152        /// Tracer process TGID.
153        tracer_tgid: i32,
154    },
155    /// Comm event, triggered when a process changes its command name.
156    Comm {
157        /// Process PID.
158        process_pid: i32,
159        /// Process TGID.
160        process_tgid: i32,
161        /// Command name (null-terminated, max 16 bytes).
162        comm: [u8; 16],
163    },
164    /// Coredump event, triggered when a process dumps core.
165    Coredump {
166        /// Process PID.
167        process_pid: i32,
168        /// Process TGID.
169        process_tgid: i32,
170        /// Parent process PID.
171        parent_pid: i32,
172        /// Parent process TGID.
173        parent_tgid: i32,
174    },
175    /// Exit event, triggered when a process exits.
176    Exit {
177        /// Process PID.
178        process_pid: i32,
179        /// Process TGID.
180        process_tgid: i32,
181        /// Exit code.
182        exit_code: u32,
183        /// Exit signal.
184        exit_signal: u32,
185        /// Parent process PID.
186        parent_pid: i32,
187        /// Parent process TGID.
188        parent_tgid: i32,
189    },
190}
191
192impl From<&ProcEvent> for ProcEventType {
193    fn from(ev: &ProcEvent) -> Self {
194        match ev {
195            ProcEvent::Ack { .. } => ProcEventType::None,
196            ProcEvent::Fork { .. } => ProcEventType::Fork,
197            ProcEvent::Exec { .. } => ProcEventType::Exec,
198            ProcEvent::Uid { .. } => ProcEventType::Uid,
199            ProcEvent::Gid { .. } => ProcEventType::Gid,
200            ProcEvent::Sid { .. } => ProcEventType::Sid,
201            ProcEvent::Ptrace { .. } => ProcEventType::Ptrace,
202            ProcEvent::Comm { .. } => ProcEventType::Comm,
203            ProcEvent::Coredump { .. } => ProcEventType::Coredump,
204            ProcEvent::Exit { exit_code, .. } => {
205                if *exit_code == 0 {
206                    ProcEventType::Exit
207                } else {
208                    ProcEventType::NonzeroExit
209                }
210            }
211        }
212    }
213}
214
215impl ToBytes for ProcEventHeader {
216    fn to_bytes(&self, buffer: &mut Cursor<Vec<u8>>) -> Result<(), SerError> {
217        ProcEventType::from(&self.event).to_bytes(buffer)?;
218        self.cpu.to_bytes(buffer)?;
219        self.timestamp_ns.to_bytes(buffer)?;
220
221        match self.event {
222            ProcEvent::Ack { err } => {
223                err.to_bytes(buffer)?;
224            }
225            ProcEvent::Fork {
226                parent_pid,
227                parent_tgid,
228                child_pid,
229                child_tgid,
230            } => {
231                parent_pid.to_bytes(buffer)?;
232                parent_tgid.to_bytes(buffer)?;
233                child_pid.to_bytes(buffer)?;
234                child_tgid.to_bytes(buffer)?;
235            }
236            ProcEvent::Exec {
237                process_pid,
238                process_tgid,
239            } => {
240                process_pid.to_bytes(buffer)?;
241                process_tgid.to_bytes(buffer)?;
242            }
243            ProcEvent::Uid {
244                process_pid,
245                process_tgid,
246                ruid,
247                euid,
248            } => {
249                process_pid.to_bytes(buffer)?;
250                process_tgid.to_bytes(buffer)?;
251                ruid.to_bytes(buffer)?;
252                euid.to_bytes(buffer)?;
253            }
254            ProcEvent::Gid {
255                process_pid,
256                process_tgid,
257                rgid,
258                egid,
259            } => {
260                process_pid.to_bytes(buffer)?;
261                process_tgid.to_bytes(buffer)?;
262                rgid.to_bytes(buffer)?;
263                egid.to_bytes(buffer)?;
264            }
265            ProcEvent::Sid {
266                process_pid,
267                process_tgid,
268            } => {
269                process_pid.to_bytes(buffer)?;
270                process_tgid.to_bytes(buffer)?;
271            }
272            ProcEvent::Ptrace {
273                process_pid,
274                process_tgid,
275                tracer_pid,
276                tracer_tgid,
277            } => {
278                process_pid.to_bytes(buffer)?;
279                process_tgid.to_bytes(buffer)?;
280                tracer_pid.to_bytes(buffer)?;
281                tracer_tgid.to_bytes(buffer)?;
282            }
283            ProcEvent::Comm {
284                process_pid,
285                process_tgid,
286                comm,
287            } => {
288                process_pid.to_bytes(buffer)?;
289                process_tgid.to_bytes(buffer)?;
290                comm.to_bytes(buffer)?;
291            }
292            ProcEvent::Coredump {
293                process_pid,
294                process_tgid,
295                parent_pid,
296                parent_tgid,
297            } => {
298                process_pid.to_bytes(buffer)?;
299                process_tgid.to_bytes(buffer)?;
300                parent_pid.to_bytes(buffer)?;
301                parent_tgid.to_bytes(buffer)?;
302            }
303            ProcEvent::Exit {
304                process_pid,
305                process_tgid,
306                exit_code,
307                exit_signal,
308                parent_pid,
309                parent_tgid,
310            } => {
311                process_pid.to_bytes(buffer)?;
312                process_tgid.to_bytes(buffer)?;
313                exit_code.to_bytes(buffer)?;
314                exit_signal.to_bytes(buffer)?;
315                parent_pid.to_bytes(buffer)?;
316                parent_tgid.to_bytes(buffer)?;
317            }
318        };
319
320        Ok(())
321    }
322}
323
324impl FromBytesWithInput for ProcEventHeader {
325    type Input = usize;
326
327    fn from_bytes_with_input(
328        buffer: &mut Cursor<impl AsRef<[u8]>>,
329        input: Self::Input,
330    ) -> Result<Self, DeError> {
331        let start = buffer.position();
332
333        trace!("Parsing ProcEventHeader at position {start} with input size {input}");
334
335        // Minimum size for header (16) + smallest event (ack: 4) is 20.
336        if input < 16 || buffer.position() as usize + input > buffer.get_ref().as_ref().len() {
337            return Err(DeError::InvalidInput(input));
338        }
339
340        // Read header fields: what (u32), cpu (u32), timestamp_ns (u64)
341        fn parse(buffer: &mut Cursor<impl AsRef<[u8]>>) -> Result<ProcEventHeader, DeError> {
342            let what_val = u32::from_bytes(buffer)?;
343            let what = ProcEventType::from(what_val);
344            let cpu = u32::from_bytes(buffer)?;
345            let timestamp_ns = u64::from_bytes(buffer)?;
346
347            let event = match what {
348                ProcEventType::None => ProcEvent::Ack {
349                    err: u32::from_bytes(buffer)?,
350                },
351                ProcEventType::Fork => ProcEvent::Fork {
352                    parent_pid: i32::from_bytes(buffer)?,
353                    parent_tgid: i32::from_bytes(buffer)?,
354                    child_pid: i32::from_bytes(buffer)?,
355                    child_tgid: i32::from_bytes(buffer)?,
356                },
357                ProcEventType::Exec => ProcEvent::Exec {
358                    process_pid: i32::from_bytes(buffer)?,
359                    process_tgid: i32::from_bytes(buffer)?,
360                },
361                ProcEventType::Uid => ProcEvent::Uid {
362                    process_pid: i32::from_bytes(buffer)?,
363                    process_tgid: i32::from_bytes(buffer)?,
364                    ruid: u32::from_bytes(buffer)?,
365                    euid: u32::from_bytes(buffer)?,
366                },
367                ProcEventType::Gid => ProcEvent::Gid {
368                    process_pid: i32::from_bytes(buffer)?,
369                    process_tgid: i32::from_bytes(buffer)?,
370                    rgid: u32::from_bytes(buffer)?,
371                    egid: u32::from_bytes(buffer)?,
372                },
373                ProcEventType::Sid => ProcEvent::Sid {
374                    process_pid: i32::from_bytes(buffer)?,
375                    process_tgid: i32::from_bytes(buffer)?,
376                },
377                ProcEventType::Ptrace => ProcEvent::Ptrace {
378                    process_pid: i32::from_bytes(buffer)?,
379                    process_tgid: i32::from_bytes(buffer)?,
380                    tracer_pid: i32::from_bytes(buffer)?,
381                    tracer_tgid: i32::from_bytes(buffer)?,
382                },
383                ProcEventType::Comm => {
384                    let process_pid = i32::from_bytes(buffer)?;
385                    let process_tgid = i32::from_bytes(buffer)?;
386                    let mut comm = [0u8; 16];
387                    buffer.read_exact(&mut comm)?;
388                    ProcEvent::Comm {
389                        process_pid,
390                        process_tgid,
391                        comm,
392                    }
393                }
394                ProcEventType::Coredump => ProcEvent::Coredump {
395                    process_pid: i32::from_bytes(buffer)?,
396                    process_tgid: i32::from_bytes(buffer)?,
397                    parent_pid: i32::from_bytes(buffer)?,
398                    parent_tgid: i32::from_bytes(buffer)?,
399                },
400                ProcEventType::Exit | ProcEventType::NonzeroExit => ProcEvent::Exit {
401                    process_pid: i32::from_bytes(buffer)?,
402                    process_tgid: i32::from_bytes(buffer)?,
403                    exit_code: u32::from_bytes(buffer)?,
404                    exit_signal: u32::from_bytes(buffer)?,
405                    parent_pid: i32::from_bytes(buffer)?,
406                    parent_tgid: i32::from_bytes(buffer)?,
407                },
408                ProcEventType::UnrecognizedConst(i) => {
409                    return Err(DeError::Msg(MsgError::new(format!(
410                        "Unrecognized Proc event type: {i} (raw value: {what_val})"
411                    ))));
412                }
413            };
414            Ok(ProcEventHeader {
415                cpu,
416                timestamp_ns,
417                event,
418            })
419        }
420
421        let event = match parse(buffer) {
422            Ok(ev) => ev,
423            Err(e) => {
424                buffer.set_position(start);
425                return Err(e);
426            }
427        };
428
429        buffer.set_position(start + input as u64);
430
431        // consume the entire len, because the kernel can pad the event data with zeros
432
433        Ok(event)
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use super::*;
440
441    fn build_endian_agnostic_response() -> Vec<u8> {
442        let mut cursor = Cursor::new(vec![]);
443        let msg = CnMsg {
444            idx: CnMsgIdx::Proc,
445            val: CnMsgVal::Proc,
446            seq: 643,
447            ack: 0,
448            len: 40,
449            flags: 0,
450            payload: ProcEventHeader {
451                cpu: 1,
452                timestamp_ns: 2504390882488,
453                event: ProcEvent::Exec {
454                    process_pid: 5759,
455                    process_tgid: 5759,
456                },
457            },
458        };
459        msg.to_bytes(&mut cursor).unwrap();
460        cursor.into_inner()
461    }
462
463    #[test]
464    fn parse_static_proc_header() {
465        let mut cursor = Cursor::new(build_endian_agnostic_response());
466
467        let len = cursor.get_ref().len();
468        let msg: CnMsg<ProcEventHeader> = CnMsg::from_bytes_with_input(&mut cursor, len).unwrap();
469
470        assert_eq!(msg.idx(), &CnMsgIdx::Proc);
471        assert_eq!(msg.val(), &CnMsgVal::Proc);
472        assert_eq!(msg.payload.cpu, 1);
473        assert_eq!(msg.payload.timestamp_ns, 2504390882488);
474        match &msg.payload.event {
475            ProcEvent::Exec {
476                process_pid,
477                process_tgid,
478            } => {
479                assert_eq!(*process_pid, 5759);
480                assert_eq!(*process_tgid, 5759);
481            }
482            _ => panic!("Expected Exec event"),
483        }
484    }
485
486    #[test]
487    fn parse_static_raw_data() {
488        let mut cursor = Cursor::new(build_endian_agnostic_response());
489
490        let len = cursor.get_ref().len();
491        let msg: CnMsg<Vec<u8>> = CnMsg::from_bytes_with_input(&mut cursor, len).unwrap();
492
493        assert_eq!(msg.idx(), &CnMsgIdx::Proc);
494        assert_eq!(msg.val(), &CnMsgVal::Proc);
495    }
496}