Skip to main content

nlink/netlink/
connector.rs

1//! Kernel connector implementation for `Connection<Connector>`.
2//!
3//! This module provides methods for receiving process events via the
4//! NETLINK_CONNECTOR protocol. Process events include fork, exec, exit,
5//! and credential changes.
6//!
7//! # Example
8//!
9//! ```ignore
10//! use nlink::netlink::{Connection, Connector};
11//! use nlink::netlink::connector::ProcEvent;
12//!
13//! // Requires CAP_NET_ADMIN
14//! let conn = Connection::<Connector>::new_proc_events().await?;
15//!
16//! loop {
17//!     match conn.recv().await? {
18//!         ProcEvent::Fork { parent_pid, child_pid, .. } => {
19//!             println!("fork: {} -> {}", parent_pid, child_pid);
20//!         }
21//!         ProcEvent::Exec { pid, .. } => {
22//!             println!("exec: {}", pid);
23//!         }
24//!         ProcEvent::Exit { pid, exit_code, .. } => {
25//!             println!("exit: {} ({})", pid, exit_code);
26//!         }
27//!         _ => {}
28//!     }
29//! }
30//! ```
31
32use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
33
34use super::connection::Connection;
35use super::error::Result;
36use super::parse::{PResult, parse_string_from_bytes, parse_u32_ne, parse_u64_ne};
37use super::protocol::{Connector, ProtocolState};
38use super::socket::NetlinkSocket;
39
40// Connector constants
41const CN_IDX_PROC: u32 = 1;
42const CN_VAL_PROC: u32 = 1;
43
44// Process event types
45const PROC_EVENT_NONE: u32 = 0x00000000;
46const PROC_EVENT_FORK: u32 = 0x00000001;
47const PROC_EVENT_EXEC: u32 = 0x00000002;
48const PROC_EVENT_UID: u32 = 0x00000004;
49const PROC_EVENT_GID: u32 = 0x00000040;
50const PROC_EVENT_SID: u32 = 0x00000080;
51const PROC_EVENT_PTRACE: u32 = 0x00000100;
52const PROC_EVENT_COMM: u32 = 0x00000200;
53const PROC_EVENT_COREDUMP: u32 = 0x40000000;
54const PROC_EVENT_EXIT: u32 = 0x80000000;
55
56// Connector message operation
57const PROC_CN_MCAST_LISTEN: u32 = 1;
58const PROC_CN_MCAST_IGNORE: u32 = 2;
59
60// Netlink header size
61const NLMSG_HDRLEN: usize = 16;
62
63/// A process lifecycle event.
64#[derive(Debug, Clone)]
65#[non_exhaustive]
66pub enum ProcEvent {
67    /// No event (acknowledgment).
68    None,
69
70    /// Process forked.
71    Fork {
72        /// Parent process ID.
73        parent_pid: u32,
74        /// Parent thread group ID.
75        parent_tgid: u32,
76        /// Child process ID.
77        child_pid: u32,
78        /// Child thread group ID.
79        child_tgid: u32,
80    },
81
82    /// Process executed a new program.
83    Exec {
84        /// Process ID.
85        pid: u32,
86        /// Thread group ID.
87        tgid: u32,
88    },
89
90    /// Process changed UID.
91    Uid {
92        /// Process ID.
93        pid: u32,
94        /// Thread group ID.
95        tgid: u32,
96        /// Real UID.
97        ruid: u32,
98        /// Effective UID.
99        euid: u32,
100    },
101
102    /// Process changed GID.
103    Gid {
104        /// Process ID.
105        pid: u32,
106        /// Thread group ID.
107        tgid: u32,
108        /// Real GID.
109        rgid: u32,
110        /// Effective GID.
111        egid: u32,
112    },
113
114    /// Process started a new session.
115    Sid {
116        /// Process ID.
117        pid: u32,
118        /// Thread group ID.
119        tgid: u32,
120    },
121
122    /// Process changed its comm (command name).
123    Comm {
124        /// Process ID.
125        pid: u32,
126        /// Thread group ID.
127        tgid: u32,
128        /// New command name.
129        comm: String,
130    },
131
132    /// Process is being traced (ptrace).
133    Ptrace {
134        /// Process ID.
135        pid: u32,
136        /// Thread group ID.
137        tgid: u32,
138        /// Tracer process ID.
139        tracer_pid: u32,
140        /// Tracer thread group ID.
141        tracer_tgid: u32,
142    },
143
144    /// Process dumped core.
145    Coredump {
146        /// Process ID.
147        pid: u32,
148        /// Thread group ID.
149        tgid: u32,
150        /// Parent process ID.
151        parent_pid: u32,
152        /// Parent thread group ID.
153        parent_tgid: u32,
154    },
155
156    /// Process exited.
157    Exit {
158        /// Process ID.
159        pid: u32,
160        /// Thread group ID.
161        tgid: u32,
162        /// Exit code.
163        exit_code: u32,
164        /// Exit signal.
165        exit_signal: u32,
166        /// Parent process ID.
167        parent_pid: u32,
168        /// Parent thread group ID.
169        parent_tgid: u32,
170    },
171
172    /// Unknown event type.
173    Unknown {
174        /// Event type code.
175        what: u32,
176    },
177}
178
179impl ProcEvent {
180    /// Get the process ID for this event, if applicable.
181    pub fn pid(&self) -> Option<u32> {
182        match self {
183            ProcEvent::Fork { child_pid, .. } => Some(*child_pid),
184            ProcEvent::Exec { pid, .. } => Some(*pid),
185            ProcEvent::Uid { pid, .. } => Some(*pid),
186            ProcEvent::Gid { pid, .. } => Some(*pid),
187            ProcEvent::Sid { pid, .. } => Some(*pid),
188            ProcEvent::Comm { pid, .. } => Some(*pid),
189            ProcEvent::Ptrace { pid, .. } => Some(*pid),
190            ProcEvent::Coredump { pid, .. } => Some(*pid),
191            ProcEvent::Exit { pid, .. } => Some(*pid),
192            ProcEvent::None | ProcEvent::Unknown { .. } => None,
193        }
194    }
195
196    /// Get the thread group ID for this event, if applicable.
197    pub fn tgid(&self) -> Option<u32> {
198        match self {
199            ProcEvent::Fork { child_tgid, .. } => Some(*child_tgid),
200            ProcEvent::Exec { tgid, .. } => Some(*tgid),
201            ProcEvent::Uid { tgid, .. } => Some(*tgid),
202            ProcEvent::Gid { tgid, .. } => Some(*tgid),
203            ProcEvent::Sid { tgid, .. } => Some(*tgid),
204            ProcEvent::Comm { tgid, .. } => Some(*tgid),
205            ProcEvent::Ptrace { tgid, .. } => Some(*tgid),
206            ProcEvent::Coredump { tgid, .. } => Some(*tgid),
207            ProcEvent::Exit { tgid, .. } => Some(*tgid),
208            ProcEvent::None | ProcEvent::Unknown { .. } => None,
209        }
210    }
211
212    /// Parse a process event from the payload after the cn_msg header.
213    ///
214    /// The input should be the data after the connector message header (20 bytes).
215    /// Used by the stream implementation.
216    pub fn parse_from_bytes(input: &[u8]) -> Option<Self> {
217        let mut input = input;
218
219        // Parse proc_event header
220        let header = ProcEventHeader::parse(&mut input).ok()?;
221
222        // Parse event-specific data based on type
223        match header.what {
224            PROC_EVENT_NONE => Some(ProcEvent::None),
225
226            PROC_EVENT_FORK => {
227                let parent_pid = parse_u32_ne(&mut input).ok()?;
228                let parent_tgid = parse_u32_ne(&mut input).ok()?;
229                let child_pid = parse_u32_ne(&mut input).ok()?;
230                let child_tgid = parse_u32_ne(&mut input).ok()?;
231                Some(ProcEvent::Fork {
232                    parent_pid,
233                    parent_tgid,
234                    child_pid,
235                    child_tgid,
236                })
237            }
238
239            PROC_EVENT_EXEC => {
240                let pid = parse_u32_ne(&mut input).ok()?;
241                let tgid = parse_u32_ne(&mut input).ok()?;
242                Some(ProcEvent::Exec { pid, tgid })
243            }
244
245            PROC_EVENT_UID => {
246                let pid = parse_u32_ne(&mut input).ok()?;
247                let tgid = parse_u32_ne(&mut input).ok()?;
248                let ruid = parse_u32_ne(&mut input).ok()?;
249                let euid = parse_u32_ne(&mut input).ok()?;
250                Some(ProcEvent::Uid {
251                    pid,
252                    tgid,
253                    ruid,
254                    euid,
255                })
256            }
257
258            PROC_EVENT_GID => {
259                let pid = parse_u32_ne(&mut input).ok()?;
260                let tgid = parse_u32_ne(&mut input).ok()?;
261                let rgid = parse_u32_ne(&mut input).ok()?;
262                let egid = parse_u32_ne(&mut input).ok()?;
263                Some(ProcEvent::Gid {
264                    pid,
265                    tgid,
266                    rgid,
267                    egid,
268                })
269            }
270
271            PROC_EVENT_SID => {
272                let pid = parse_u32_ne(&mut input).ok()?;
273                let tgid = parse_u32_ne(&mut input).ok()?;
274                Some(ProcEvent::Sid { pid, tgid })
275            }
276
277            PROC_EVENT_COMM => {
278                let pid = parse_u32_ne(&mut input).ok()?;
279                let tgid = parse_u32_ne(&mut input).ok()?;
280                // comm is 16 bytes
281                if input.len() < 16 {
282                    return None;
283                }
284                let comm = parse_string_from_bytes(&input[..16]);
285                Some(ProcEvent::Comm { pid, tgid, comm })
286            }
287
288            PROC_EVENT_PTRACE => {
289                let pid = parse_u32_ne(&mut input).ok()?;
290                let tgid = parse_u32_ne(&mut input).ok()?;
291                let tracer_pid = parse_u32_ne(&mut input).ok()?;
292                let tracer_tgid = parse_u32_ne(&mut input).ok()?;
293                Some(ProcEvent::Ptrace {
294                    pid,
295                    tgid,
296                    tracer_pid,
297                    tracer_tgid,
298                })
299            }
300
301            PROC_EVENT_COREDUMP => {
302                let pid = parse_u32_ne(&mut input).ok()?;
303                let tgid = parse_u32_ne(&mut input).ok()?;
304                let parent_pid = parse_u32_ne(&mut input).ok()?;
305                let parent_tgid = parse_u32_ne(&mut input).ok()?;
306                Some(ProcEvent::Coredump {
307                    pid,
308                    tgid,
309                    parent_pid,
310                    parent_tgid,
311                })
312            }
313
314            PROC_EVENT_EXIT => {
315                let pid = parse_u32_ne(&mut input).ok()?;
316                let tgid = parse_u32_ne(&mut input).ok()?;
317                let exit_code = parse_u32_ne(&mut input).ok()?;
318                let exit_signal = parse_u32_ne(&mut input).ok()?;
319                let parent_pid = parse_u32_ne(&mut input).ok()?;
320                let parent_tgid = parse_u32_ne(&mut input).ok()?;
321                Some(ProcEvent::Exit {
322                    pid,
323                    tgid,
324                    exit_code,
325                    exit_signal,
326                    parent_pid,
327                    parent_tgid,
328                })
329            }
330
331            _ => Some(ProcEvent::Unknown { what: header.what }),
332        }
333    }
334}
335
336/// cn_msg header structure (20 bytes).
337#[repr(C)]
338#[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
339struct CnMsg {
340    /// Connector ID (idx, val)
341    idx: u32,
342    val: u32,
343    /// Sequence number
344    seq: u32,
345    /// Acknowledgment sequence
346    ack: u32,
347    /// Payload length
348    len: u16,
349    /// Flags
350    flags: u16,
351}
352
353impl CnMsg {
354    /// Safe serialization using zerocopy.
355    fn as_bytes(&self) -> &[u8] {
356        <Self as IntoBytes>::as_bytes(self)
357    }
358
359    /// Parse a cn_msg from bytes using zerocopy.
360    /// Returns the parsed struct and the remaining bytes.
361    fn from_bytes(data: &[u8]) -> Option<(&Self, &[u8])> {
362        Self::ref_from_prefix(data).ok()
363    }
364}
365
366/// proc_event header (what + cpu + timestamp).
367#[derive(Debug, Clone, Copy)]
368struct ProcEventHeader {
369    what: u32,
370    #[allow(dead_code)]
371    cpu: u32,
372    #[allow(dead_code)]
373    timestamp_ns: u64,
374}
375
376impl ProcEventHeader {
377    fn parse(input: &mut &[u8]) -> PResult<Self> {
378        let what = parse_u32_ne(input)?;
379        let cpu = parse_u32_ne(input)?;
380        let timestamp_ns = parse_u64_ne(input)?;
381        Ok(Self {
382            what,
383            cpu,
384            timestamp_ns,
385        })
386    }
387}
388
389impl Connection<Connector> {
390    /// Create a new connector and register for process events.
391    ///
392    /// This requires `CAP_NET_ADMIN` capability.
393    ///
394    /// # Example
395    ///
396    /// ```ignore
397    /// use nlink::netlink::{Connection, Connector};
398    ///
399    /// let conn = Connection::<Connector>::new().await?;
400    /// ```
401    pub async fn new() -> Result<Self> {
402        let mut socket = NetlinkSocket::new(Connector::PROTOCOL)?;
403
404        // Join the proc connector multicast group
405        socket.add_membership(CN_IDX_PROC)?;
406
407        let conn = Self::from_parts(socket, Connector);
408
409        // Send registration message to enable proc events
410        conn.send_proc_control(PROC_CN_MCAST_LISTEN).await?;
411
412        Ok(conn)
413    }
414
415    /// Unregister from process events.
416    ///
417    /// After calling this, no more events will be received.
418    pub async fn unregister(&self) -> Result<()> {
419        self.send_proc_control(PROC_CN_MCAST_IGNORE).await
420    }
421
422    /// Send a process connector control message.
423    async fn send_proc_control(&self, op: u32) -> Result<()> {
424        let seq = self.socket().next_seq();
425        let pid = self.socket().pid();
426
427        // Build the message
428        let mut buf = Vec::with_capacity(64);
429
430        // Netlink header (16 bytes)
431        let msg_len = NLMSG_HDRLEN + std::mem::size_of::<CnMsg>() + 4;
432        buf.extend_from_slice(&(msg_len as u32).to_ne_bytes()); // nlmsg_len
433        buf.extend_from_slice(&0x0u16.to_ne_bytes()); // nlmsg_type (NLMSG_DONE)
434        buf.extend_from_slice(&0x0u16.to_ne_bytes()); // nlmsg_flags
435        buf.extend_from_slice(&seq.to_ne_bytes()); // nlmsg_seq
436        buf.extend_from_slice(&pid.to_ne_bytes()); // nlmsg_pid
437
438        // cn_msg header
439        let cn_msg = CnMsg {
440            idx: CN_IDX_PROC,
441            val: CN_VAL_PROC,
442            seq: 0,
443            ack: 0,
444            len: 4,
445            flags: 0,
446        };
447        buf.extend_from_slice(cn_msg.as_bytes());
448
449        // Payload: operation
450        buf.extend_from_slice(&op.to_ne_bytes());
451
452        self.socket().send(&buf).await?;
453        Ok(())
454    }
455
456    /// Receive the next process event.
457    ///
458    /// This method blocks until an event is available.
459    ///
460    /// # Example
461    ///
462    /// ```ignore
463    /// use nlink::netlink::{Connection, Connector};
464    /// use nlink::netlink::connector::ProcEvent;
465    ///
466    /// let conn = Connection::<Connector>::new_proc_events().await?;
467    ///
468    /// loop {
469    ///     let event = conn.recv().await?;
470    ///     if let Some(pid) = event.pid() {
471    ///         println!("Event for PID {}: {:?}", pid, event);
472    ///     }
473    /// }
474    /// ```
475    pub async fn recv(&self) -> Result<ProcEvent> {
476        loop {
477            let data = self.socket().recv_msg().await?;
478
479            if let Some(event) = self.parse_proc_event(&data) {
480                return Ok(event);
481            }
482            // Invalid message, try again
483        }
484    }
485
486    /// Parse a process event from raw message data.
487    fn parse_proc_event(&self, data: &[u8]) -> Option<ProcEvent> {
488        // Skip netlink header (16 bytes)
489        if data.len() < NLMSG_HDRLEN {
490            return None;
491        }
492        let after_nlhdr = &data[NLMSG_HDRLEN..];
493
494        // Parse cn_msg header using zerocopy
495        let (_cn_msg, mut input) = CnMsg::from_bytes(after_nlhdr)?;
496
497        // Parse proc_event header
498        let header = ProcEventHeader::parse(&mut input).ok()?;
499
500        // Parse event-specific data based on type
501        match header.what {
502            PROC_EVENT_NONE => Some(ProcEvent::None),
503
504            PROC_EVENT_FORK => {
505                let parent_pid = parse_u32_ne(&mut input).ok()?;
506                let parent_tgid = parse_u32_ne(&mut input).ok()?;
507                let child_pid = parse_u32_ne(&mut input).ok()?;
508                let child_tgid = parse_u32_ne(&mut input).ok()?;
509                Some(ProcEvent::Fork {
510                    parent_pid,
511                    parent_tgid,
512                    child_pid,
513                    child_tgid,
514                })
515            }
516
517            PROC_EVENT_EXEC => {
518                let pid = parse_u32_ne(&mut input).ok()?;
519                let tgid = parse_u32_ne(&mut input).ok()?;
520                Some(ProcEvent::Exec { pid, tgid })
521            }
522
523            PROC_EVENT_UID => {
524                let pid = parse_u32_ne(&mut input).ok()?;
525                let tgid = parse_u32_ne(&mut input).ok()?;
526                let ruid = parse_u32_ne(&mut input).ok()?;
527                let euid = parse_u32_ne(&mut input).ok()?;
528                Some(ProcEvent::Uid {
529                    pid,
530                    tgid,
531                    ruid,
532                    euid,
533                })
534            }
535
536            PROC_EVENT_GID => {
537                let pid = parse_u32_ne(&mut input).ok()?;
538                let tgid = parse_u32_ne(&mut input).ok()?;
539                let rgid = parse_u32_ne(&mut input).ok()?;
540                let egid = parse_u32_ne(&mut input).ok()?;
541                Some(ProcEvent::Gid {
542                    pid,
543                    tgid,
544                    rgid,
545                    egid,
546                })
547            }
548
549            PROC_EVENT_SID => {
550                let pid = parse_u32_ne(&mut input).ok()?;
551                let tgid = parse_u32_ne(&mut input).ok()?;
552                Some(ProcEvent::Sid { pid, tgid })
553            }
554
555            PROC_EVENT_COMM => {
556                let pid = parse_u32_ne(&mut input).ok()?;
557                let tgid = parse_u32_ne(&mut input).ok()?;
558                // comm is 16 bytes
559                if input.len() < 16 {
560                    return None;
561                }
562                let comm = parse_string_from_bytes(&input[..16]);
563                Some(ProcEvent::Comm { pid, tgid, comm })
564            }
565
566            PROC_EVENT_PTRACE => {
567                let pid = parse_u32_ne(&mut input).ok()?;
568                let tgid = parse_u32_ne(&mut input).ok()?;
569                let tracer_pid = parse_u32_ne(&mut input).ok()?;
570                let tracer_tgid = parse_u32_ne(&mut input).ok()?;
571                Some(ProcEvent::Ptrace {
572                    pid,
573                    tgid,
574                    tracer_pid,
575                    tracer_tgid,
576                })
577            }
578
579            PROC_EVENT_COREDUMP => {
580                let pid = parse_u32_ne(&mut input).ok()?;
581                let tgid = parse_u32_ne(&mut input).ok()?;
582                let parent_pid = parse_u32_ne(&mut input).ok()?;
583                let parent_tgid = parse_u32_ne(&mut input).ok()?;
584                Some(ProcEvent::Coredump {
585                    pid,
586                    tgid,
587                    parent_pid,
588                    parent_tgid,
589                })
590            }
591
592            PROC_EVENT_EXIT => {
593                let pid = parse_u32_ne(&mut input).ok()?;
594                let tgid = parse_u32_ne(&mut input).ok()?;
595                let exit_code = parse_u32_ne(&mut input).ok()?;
596                let exit_signal = parse_u32_ne(&mut input).ok()?;
597                let parent_pid = parse_u32_ne(&mut input).ok()?;
598                let parent_tgid = parse_u32_ne(&mut input).ok()?;
599                Some(ProcEvent::Exit {
600                    pid,
601                    tgid,
602                    exit_code,
603                    exit_signal,
604                    parent_pid,
605                    parent_tgid,
606                })
607            }
608
609            _ => Some(ProcEvent::Unknown { what: header.what }),
610        }
611    }
612}
613
614#[cfg(test)]
615mod tests {
616    use super::*;
617
618    #[test]
619    fn proc_event_pid() {
620        let fork = ProcEvent::Fork {
621            parent_pid: 1,
622            parent_tgid: 1,
623            child_pid: 100,
624            child_tgid: 100,
625        };
626        assert_eq!(fork.pid(), Some(100));
627        assert_eq!(fork.tgid(), Some(100));
628
629        let exit = ProcEvent::Exit {
630            pid: 200,
631            tgid: 200,
632            exit_code: 0,
633            exit_signal: 17,
634            parent_pid: 1,
635            parent_tgid: 1,
636        };
637        assert_eq!(exit.pid(), Some(200));
638    }
639}