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