cnproc/
lib.rs

1mod binding;
2use binding::{
3    cn_msg, nlmsghdr, proc_cn_mcast_op, sockaddr_nl, CN_IDX_PROC, NETLINK_CONNECTOR,
4    PROC_CN_MCAST_LISTEN,
5};
6use libc;
7use std::collections::VecDeque;
8use std::io::{Error, Result};
9
10// these are some macros defined in netlink.h
11
12#[inline]
13fn nlmsg_align(len: usize) -> usize {
14    (len + 3) & !3
15}
16
17#[inline]
18fn nlmsg_hdrlen() -> usize {
19    nlmsg_align(std::mem::size_of::<nlmsghdr>())
20}
21
22#[inline]
23fn nlmsg_length(len: usize) -> usize {
24    len + nlmsg_hdrlen()
25}
26
27/// Events we are interested in
28#[derive(Debug)]
29pub enum PidEvent {
30    ///  PROC_EVENT_EXEC
31    Exec(libc::c_int),
32    ///  PROC_EVENT_FORK
33    Fork(libc::c_int),
34    /// PROC_EVENT_COREDUMP
35    Coredump(libc::c_int),
36    /// PROC_EVENT_EXIT
37    Exit(libc::c_int),
38}
39
40/// The monitor will watch for process creation or destruction events
41/// comming from the kernel
42#[derive(Debug)]
43pub struct PidMonitor {
44    fd: libc::c_int,
45    id: u32,
46    queue: VecDeque<PidEvent>,
47}
48
49impl PidMonitor {
50    /// Creates a new PidMonitor
51    pub fn new() -> Result<PidMonitor> {
52        PidMonitor::from_id(std::process::id())
53    }
54
55    /// Creates a new PidMonitor, the netlink socket will be created
56    /// with the given id instead of `std::process::id()`
57    pub fn from_id(id: u32) -> Result<PidMonitor> {
58        let fd = unsafe {
59            libc::socket(
60                libc::PF_NETLINK,
61                libc::SOCK_DGRAM,
62                // for some reason bindgen doesn't make this
63                // a libc::c_int
64                NETLINK_CONNECTOR as i32,
65            )
66        };
67        let mut nl = unsafe { std::mem::zeroed::<sockaddr_nl>() };
68        nl.nl_pid = id;
69        // Again this is an issue of bindgen vs libc
70        nl.nl_family = libc::AF_NETLINK as u16;
71        nl.nl_groups = CN_IDX_PROC;
72        if unsafe {
73            libc::bind(
74                fd,
75                &nl as *const sockaddr_nl as _,
76                std::mem::size_of_val(&nl) as _,
77            )
78        } < 0
79        {
80            return Err(Error::last_os_error());
81        }
82        let mut monitor = PidMonitor {
83            fd,
84            id,
85            queue: VecDeque::new(),
86        };
87        monitor.listen()?;
88        return Ok(monitor);
89    }
90
91    /// Signals to the kernel we are ready to start receiving events
92    fn listen(&mut self) -> Result<()> {
93        let val = true as libc::c_int;
94        if unsafe {
95            libc::setsockopt(
96                self.fd,
97                libc::SOL_NETLINK,
98                binding::NETLINK_NO_ENOBUFS as i32,
99                &val as *const libc::c_int as _,
100                std::mem::size_of_val(&val) as _,
101            )
102        } < 0
103        {
104            return Err(std::io::Error::last_os_error());
105        }
106        let mut iov_vec = Vec::<libc::iovec>::new();
107        // Set nlmsghdr
108        let mut msghdr: nlmsghdr = unsafe { std::mem::zeroed() };
109        msghdr.nlmsg_len =
110            nlmsg_length(std::mem::size_of::<cn_msg>() + std::mem::size_of::<proc_cn_mcast_op>())
111                as u32;
112        msghdr.nlmsg_pid = self.id;
113        //Another mismatch
114        msghdr.nlmsg_type = binding::NLMSG_DONE as u16;
115        iov_vec.push(libc::iovec {
116            iov_len: std::mem::size_of::<nlmsghdr>(),
117            iov_base: &msghdr as *const nlmsghdr as _,
118        });
119        // Set cn_msg
120        let mut cnmesg: cn_msg = unsafe { std::mem::zeroed() };
121        cnmesg.id.idx = CN_IDX_PROC;
122        cnmesg.id.val = binding::CN_VAL_PROC;
123        cnmesg.len = std::mem::size_of::<proc_cn_mcast_op>() as u16;
124        iov_vec.push(libc::iovec {
125            iov_len: std::mem::size_of::<cn_msg>(),
126            iov_base: &cnmesg as *const cn_msg as _,
127        });
128        let op = PROC_CN_MCAST_LISTEN;
129        iov_vec.push(libc::iovec {
130            iov_len: std::mem::size_of_val(&op),
131            iov_base: &op as *const proc_cn_mcast_op as _,
132        });
133        if unsafe { libc::writev(self.fd, iov_vec.as_ptr() as _, 3) } < 0 {
134            Err(Error::last_os_error())
135        } else {
136            Ok(())
137        }
138    }
139
140    /// Gets the next event or events comming the netlink socket
141    fn get_events(&mut self) -> Result<()> {
142        let page_size = std::cmp::min(unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as usize }, 8192);
143        let mut buffer = Vec::<u32>::with_capacity(page_size);
144        let buff_size = buffer.capacity();
145        unsafe {
146            buffer.set_len(buff_size);
147        }
148        let len = unsafe { libc::recv(self.fd, buffer.as_mut_ptr() as _, buff_size * 4, 0) };
149        if len < 0 {
150            return Err(Error::last_os_error());
151        }
152        let mut header = buffer.as_ptr() as *const nlmsghdr;
153        let mut len = len as usize;
154        loop {
155            // NLMSG_OK
156            if len < nlmsg_hdrlen() {
157                break;
158            }
159            let msg_len = unsafe { (*header).nlmsg_len } as usize;
160            if len < msg_len {
161                break;
162            }
163            let msg_type = unsafe { (*header).nlmsg_type } as u32;
164            match msg_type {
165                binding::NLMSG_ERROR | binding::NLMSG_NOOP => continue,
166                _ => {
167                    if let Some(pidevent) = unsafe { parse_msg(header) } {
168                        self.queue.push_back(pidevent)
169                    }
170                }
171            };
172            // NLSMSG_NEXT
173            let aligned_len = nlmsg_align(msg_len);
174            header = (header as usize + aligned_len) as *const nlmsghdr;
175            match len.checked_sub(aligned_len) {
176                Some(v) => len = v,
177                None => break,
178            };
179        }
180        Ok(())
181    }
182
183    /// Returns events received.
184    pub fn recv(&mut self) -> Option<PidEvent> {
185        if self.queue.is_empty() {
186            match self.get_events() {
187                Ok(_) => self.queue.pop_front(),
188                Err(_) => None,
189            }
190        } else {
191            self.queue.pop_front()
192        }
193    }
194}
195
196unsafe fn parse_msg(header: *const nlmsghdr) -> Option<PidEvent> {
197    let msg = (header as usize + nlmsg_length(0)) as *const cn_msg;
198    if (*msg).id.idx != binding::CN_IDX_PROC || (*msg).id.val != binding::CN_VAL_PROC {
199        return None;
200    };
201    let proc_ev = (*msg).data.as_ptr() as *const binding::proc_event;
202    match (*proc_ev).what {
203        binding::PROC_EVENT_FORK => {
204            let pid = (*proc_ev).event_data.fork.child_pid;
205            Some(PidEvent::Fork(pid))
206        }
207        binding::PROC_EVENT_EXEC => {
208            let pid = (*proc_ev).event_data.exec.process_pid;
209            Some(PidEvent::Exec(pid))
210        }
211        binding::PROC_EVENT_EXIT => {
212            let pid = (*proc_ev).event_data.exit.process_pid;
213            Some(PidEvent::Exit(pid))
214        }
215        binding::PROC_EVENT_COREDUMP => {
216            let pid = (*proc_ev).event_data.coredump.process_pid;
217            Some(PidEvent::Coredump(pid))
218        }
219        _ => None,
220    }
221}
222
223impl Drop for PidMonitor {
224    fn drop(&mut self) {
225        unsafe { libc::close(self.fd) };
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232    #[test]
233    fn it_works() {}
234}