1use crate::consts::*;
19use crate::error::{Error, Result};
20
21#[inline]
27fn read_u32(buf: &[u8], off: usize) -> u32 {
28 let arr: [u8; 4] = buf[off..off + 4].try_into().unwrap();
29 u32::from_ne_bytes(arr)
30}
31
32#[inline]
34fn read_u16(buf: &[u8], off: usize) -> u16 {
35 let arr: [u8; 2] = buf[off..off + 2].try_into().unwrap();
36 u16::from_ne_bytes(arr)
37}
38
39#[inline]
41fn read_i32(buf: &[u8], off: usize) -> i32 {
42 let arr: [u8; 4] = buf[off..off + 4].try_into().unwrap();
43 i32::from_ne_bytes(arr)
44}
45
46#[inline]
48fn read_u64(buf: &[u8], off: usize) -> u64 {
49 let arr: [u8; 8] = buf[off..off + 8].try_into().unwrap();
50 u64::from_ne_bytes(arr)
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
120pub enum ProcEvent {
121 Exec {
123 pid: u32,
124 tgid: u32,
125 },
126 Fork {
128 parent_pid: u32,
129 parent_tgid: u32,
130 child_pid: u32,
131 child_tgid: u32,
132 },
133 Exit {
135 pid: u32,
136 tgid: u32,
137 exit_code: u32,
138 exit_signal: u32,
139 },
140 Uid {
142 pid: u32,
143 tgid: u32,
144 ruid: u32,
145 euid: u32,
146 },
147 Gid {
149 pid: u32,
150 tgid: u32,
151 rgid: u32,
152 egid: u32,
153 },
154 Sid {
156 pid: u32,
157 tgid: u32,
158 },
159 Ptrace {
161 pid: u32,
162 tgid: u32,
163 tracer_pid: u32,
164 tracer_tgid: u32,
165 },
166 Comm {
168 pid: u32,
169 tgid: u32,
170 comm: [u8; 16],
172 },
173 Coredump {
175 pid: u32,
176 tgid: u32,
177 },
178 Unknown {
180 what: u32,
182 raw_data: Vec<u8>,
184 },
185}
186
187pub fn parse_netlink_message(payload: &[u8], len: usize) -> Result<Option<ProcEvent>> {
206 let payload = &payload[..len];
207
208 if payload.len() < SIZE_NLMSGHDR {
209 return Err(Error::Truncated);
210 }
211
212 let nlmsg_type = read_u16(payload, 4);
213 let nlmsg_len = read_u32(payload, 0) as usize;
214
215 if nlmsg_len > payload.len() {
216 return Err(Error::Truncated);
217 }
218
219 match nlmsg_type {
220 NLMSG_NOOP => Ok(None),
221 NLMSG_DONE if nlmsg_len == SIZE_NLMSGHDR => Ok(None),
222 NLMSG_ERROR => {
223 let errno = read_i32(payload, SIZE_NLMSGHDR);
225 if errno == 0 {
226 return Ok(None);
228 }
229 let pos_errno = errno.checked_neg().unwrap_or(errno);
232 Err(Error::Os(std::io::Error::from_raw_os_error(pos_errno)))
233 }
234 NLMSG_OVERRUN => Err(Error::Overrun),
235 _ => {
236 let cn_offset = nlmsg_hdrlen();
239 if nlmsg_len < cn_offset {
240 return Err(Error::Truncated);
241 }
242 let cn_payload = &payload[cn_offset..nlmsg_len];
243 parse_cn_msg(cn_payload).map(Some)
244 }
245 }
246}
247
248fn parse_cn_msg(buf: &[u8]) -> Result<ProcEvent> {
250 if buf.len() < SIZE_CN_MSG {
251 return Err(Error::Truncated);
252 }
253
254 let idx = read_u32(buf, 0);
255 let val = read_u32(buf, 4);
256
257 if idx != CN_IDX_PROC || val != CN_VAL_PROC {
259 return Err(Error::Truncated);
260 }
261
262 let data_len = read_u16(buf, 16) as usize;
263
264 let proc_off = SIZE_CN_MSG;
266 let proc_data = if buf.len() >= proc_off + data_len {
267 &buf[proc_off..proc_off + data_len]
268 } else {
269 return Err(Error::Truncated);
270 };
271
272 parse_proc_event(proc_data)
273}
274
275fn parse_proc_event(buf: &[u8]) -> Result<ProcEvent> {
277 if buf.len() < PROC_EVENT_HEADER_SIZE {
278 return Err(Error::Truncated);
279 }
280
281 let what = read_u32(buf, 0);
282 let _cpu = read_u32(buf, 4);
283 let _timestamp_ns = read_u64(buf, 8);
284
285 let data = &buf[PROC_EVENT_HEADER_SIZE..];
286
287 match what {
288 PROC_EVENT_EXEC => {
289 if data.len() < SIZE_EXEC_EVENT {
290 return Err(Error::Truncated);
291 }
292 Ok(ProcEvent::Exec {
293 pid: read_i32(data, EXEC_PID) as u32,
294 tgid: read_i32(data, EXEC_TGID) as u32,
295 })
296 }
297
298 PROC_EVENT_FORK => {
299 if data.len() < SIZE_FORK_EVENT {
300 return Err(Error::Truncated);
301 }
302 Ok(ProcEvent::Fork {
303 parent_pid: read_i32(data, FORK_PARENT_PID) as u32,
304 parent_tgid: read_i32(data, FORK_PARENT_TGID) as u32,
305 child_pid: read_i32(data, FORK_CHILD_PID) as u32,
306 child_tgid: read_i32(data, FORK_CHILD_TGID) as u32,
307 })
308 }
309
310 PROC_EVENT_EXIT => {
311 if data.len() < SIZE_EXIT_EVENT {
312 return Err(Error::Truncated);
313 }
314 Ok(ProcEvent::Exit {
315 pid: read_i32(data, EXIT_PID) as u32,
316 tgid: read_i32(data, EXIT_TGID) as u32,
317 exit_code: read_u32(data, EXIT_CODE),
318 exit_signal: read_u32(data, EXIT_SIGNAL),
319 })
320 }
321
322 PROC_EVENT_UID => {
323 if data.len() < SIZE_ID_EVENT {
324 return Err(Error::Truncated);
325 }
326 Ok(ProcEvent::Uid {
327 pid: read_i32(data, ID_PID) as u32,
328 tgid: read_i32(data, ID_TGID) as u32,
329 ruid: read_u32(data, ID_RUID_RGID),
330 euid: read_u32(data, ID_EUID_EGID),
331 })
332 }
333
334 PROC_EVENT_GID => {
335 if data.len() < SIZE_ID_EVENT {
336 return Err(Error::Truncated);
337 }
338 Ok(ProcEvent::Gid {
339 pid: read_i32(data, ID_PID) as u32,
340 tgid: read_i32(data, ID_TGID) as u32,
341 rgid: read_u32(data, ID_RUID_RGID),
342 egid: read_u32(data, ID_EUID_EGID),
343 })
344 }
345
346 PROC_EVENT_SID => {
347 if data.len() < SIZE_SID_EVENT {
348 return Err(Error::Truncated);
349 }
350 Ok(ProcEvent::Sid {
351 pid: read_i32(data, SID_PID) as u32,
352 tgid: read_i32(data, SID_TGID) as u32,
353 })
354 }
355
356 PROC_EVENT_PTRACE => {
357 if data.len() < SIZE_PTRACE_EVENT {
358 return Err(Error::Truncated);
359 }
360 Ok(ProcEvent::Ptrace {
361 pid: read_i32(data, PTRACE_PID) as u32,
362 tgid: read_i32(data, PTRACE_TGID) as u32,
363 tracer_pid: read_i32(data, PTRACE_TRACER_PID) as u32,
364 tracer_tgid: read_i32(data, PTRACE_TRACER_TGID) as u32,
365 })
366 }
367
368 PROC_EVENT_COMM => {
369 if data.len() < SIZE_COMM_EVENT {
370 return Err(Error::Truncated);
371 }
372 let mut comm = [0u8; 16];
373 comm.copy_from_slice(&data[COMM_DATA..COMM_DATA + 16]);
374 Ok(ProcEvent::Comm {
375 pid: read_i32(data, COMM_PID) as u32,
376 tgid: read_i32(data, COMM_TGID) as u32,
377 comm,
378 })
379 }
380
381 PROC_EVENT_COREDUMP => {
382 if data.len() < SIZE_COREDUMP_EVENT {
383 return Err(Error::Truncated);
384 }
385 Ok(ProcEvent::Coredump {
386 pid: read_i32(data, COREDUMP_PID) as u32,
387 tgid: read_i32(data, COREDUMP_TGID) as u32,
388 })
389 }
390
391 _ => {
392 Ok(ProcEvent::Unknown {
394 what,
395 raw_data: data.to_vec(),
396 })
397 }
398 }
399}
400
401impl std::fmt::Display for ProcEvent {
406 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407 match self {
408 ProcEvent::Exec { pid, tgid } => write!(f, "EXEC pid={pid} tgid={tgid}"),
409 ProcEvent::Fork {
410 parent_pid,
411 parent_tgid,
412 child_pid,
413 child_tgid,
414 } => write!(
415 f,
416 "FORK parent=({parent_pid},{parent_tgid}) child=({child_pid},{child_tgid})"
417 ),
418 ProcEvent::Exit {
419 pid,
420 tgid,
421 exit_code,
422 exit_signal,
423 } => write!(
424 f,
425 "EXIT pid={pid} tgid={tgid} code={exit_code} signal={exit_signal}"
426 ),
427 ProcEvent::Uid {
428 pid, tgid, ruid, euid,
429 } => write!(f, "UID pid={pid} tgid={tgid} ruid={ruid} euid={euid}"),
430 ProcEvent::Gid {
431 pid, tgid, rgid, egid,
432 } => write!(f, "GID pid={pid} tgid={tgid} rgid={rgid} egid={egid}"),
433 ProcEvent::Sid { pid, tgid } => write!(f, "SID pid={pid} tgid={tgid}"),
434 ProcEvent::Ptrace {
435 pid,
436 tgid,
437 tracer_pid,
438 tracer_tgid,
439 } => write!(
440 f,
441 "PTRACE pid={pid} tgid={tgid} tracer=({tracer_pid},{tracer_tgid})"
442 ),
443 ProcEvent::Comm {
444 pid,
445 tgid,
446 comm,
447 } => {
448 let end = comm.iter().position(|&b| b == 0).unwrap_or(16);
450 let name = std::str::from_utf8(&comm[..end]).unwrap_or("<invalid>");
451 write!(f, "COMM pid={pid} tgid={tgid} name=\"{name}\"")
452 }
453 ProcEvent::Coredump { pid, tgid } => {
454 write!(f, "COREDUMP pid={pid} tgid={tgid}")
455 }
456 ProcEvent::Unknown { what, raw_data } => {
457 write!(f, "UNKNOWN what=0x{what:08x} len={}", raw_data.len())
458 }
459 }
460 }
461}
462
463pub struct NetlinkMessageIter<'a> {
472 buf: &'a [u8],
473 pos: usize,
474 len: usize,
475}
476
477impl<'a> NetlinkMessageIter<'a> {
478 pub fn new(buf: &'a [u8], len: usize) -> Self {
480 NetlinkMessageIter {
481 buf,
482 pos: 0,
483 len,
484 }
485 }
486}
487
488impl<'a> Iterator for NetlinkMessageIter<'a> {
489 type Item = Result<Option<ProcEvent>>;
490
491 fn next(&mut self) -> Option<Self::Item> {
492 if self.pos >= self.len {
493 return None;
494 }
495
496 let remaining = self.len - self.pos;
497 if remaining < SIZE_NLMSGHDR {
498 return Some(Err(Error::Truncated));
499 }
500
501 let nlmsg_len = read_u32(&self.buf[self.pos..], 0) as usize;
502 if nlmsg_len < SIZE_NLMSGHDR || nlmsg_len > remaining {
503 return Some(Err(Error::Truncated));
504 }
505
506 let msg_slice = &self.buf[self.pos..self.pos + nlmsg_len];
507 let nlmsg_type = read_u16(msg_slice, 4);
508
509 if nlmsg_type == NLMSG_DONE && nlmsg_len == SIZE_NLMSGHDR {
515 self.pos = self.len; return None; }
518
519 let result = parse_netlink_message(msg_slice, nlmsg_len);
521
522 self.pos += nlmsg_align(nlmsg_len);
524
525 Some(result)
526 }
527}
528
529use crate::socket::ProcConnector;
534
535impl ProcConnector {
536 pub fn recv(&self, buf: &mut [u8]) -> Result<ProcEvent> {
571 self.recv_impl(buf)
572 }
573
574 pub fn recv_timeout(&self, buf: &mut [u8], timeout: std::time::Duration) -> Result<Option<ProcEvent>> {
586 if buf.len() < SIZE_NLMSGHDR {
587 return Err(Error::BufferTooSmall { needed: SIZE_NLMSGHDR });
588 }
589
590 loop {
591 let n = match self.recv_raw_timeout(buf, timeout) {
592 Ok(Some(n)) => n,
593 Ok(None) => return Ok(None),
594 Err(Error::WouldBlock) => {
595 return Ok(None);
598 }
599 Err(e) => return Err(e),
600 };
601
602 let iter = NetlinkMessageIter::new(buf, n);
604 for msg in iter {
605 match msg? {
606 Some(event) => return Ok(Some(event)),
607 None => continue, }
609 }
610 }
612 }
613
614 fn recv_impl(&self, buf: &mut [u8]) -> Result<ProcEvent> {
619 if buf.len() < SIZE_NLMSGHDR {
620 return Err(Error::BufferTooSmall {
621 needed: SIZE_NLMSGHDR,
622 });
623 }
624
625 loop {
626 let n = match self.recv_raw(buf) {
627 Ok(n) => n,
628 Err(Error::WouldBlock) => {
629 return Err(Error::Os(std::io::Error::new(
633 std::io::ErrorKind::WouldBlock,
634 "socket is non-blocking, use AsyncFd to wait for readiness",
635 )));
636 }
637 Err(e) => return Err(e),
638 };
639
640 let iter = NetlinkMessageIter::new(buf, n);
642 for msg in iter {
643 match msg? {
644 Some(event) => return Ok(event),
645 None => continue, }
647 }
648
649 }
652 }
653
654
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660
661 fn make_proc_event_payload(what: u32, event_data: &[u8]) -> Vec<u8> {
666 let mut buf = Vec::with_capacity(PROC_EVENT_HEADER_SIZE + event_data.len());
667 buf.extend_from_slice(&what.to_ne_bytes()); buf.extend_from_slice(&0u32.to_ne_bytes()); buf.extend_from_slice(&0u64.to_ne_bytes()); buf.extend_from_slice(event_data);
671 buf
672 }
673
674 fn make_cn_msg(data: &[u8]) -> Vec<u8> {
675 let mut buf = Vec::with_capacity(SIZE_CN_MSG + data.len());
676 buf.extend_from_slice(&CN_IDX_PROC.to_ne_bytes()); buf.extend_from_slice(&CN_VAL_PROC.to_ne_bytes()); buf.extend_from_slice(&0u32.to_ne_bytes()); buf.extend_from_slice(&0u32.to_ne_bytes()); buf.extend_from_slice(&(data.len() as u16).to_ne_bytes()); buf.extend_from_slice(&0u16.to_ne_bytes()); buf.extend_from_slice(data);
683 buf
684 }
685
686 fn make_netlink_message(nlmsg_type: u16, payload: &[u8]) -> Vec<u8> {
687 let hdr_len = nlmsg_hdrlen();
688 let total_len = nlmsg_length(payload.len());
689
690 let mut buf = vec![0u8; total_len];
691 buf[0..4].copy_from_slice(&(total_len as u32).to_ne_bytes()); buf[4..6].copy_from_slice(&nlmsg_type.to_ne_bytes()); buf[6..8].copy_from_slice(&0u16.to_ne_bytes()); buf[8..12].copy_from_slice(&0u32.to_ne_bytes()); buf[12..16].copy_from_slice(&0u32.to_ne_bytes()); buf[hdr_len..hdr_len + payload.len()].copy_from_slice(payload);
697
698 buf
699 }
700
701 fn make_full_message(what: u32, event_data: &[u8]) -> Vec<u8> {
702 let proc_payload = make_proc_event_payload(what, event_data);
703 let cn_payload = make_cn_msg(&proc_payload);
704 make_netlink_message(NLMSG_MIN_TYPE, &cn_payload)
705 }
706
707 #[test]
708 fn parse_exec() {
709 let data = [
710 42i32.to_ne_bytes(), 100i32.to_ne_bytes(), ]
713 .concat();
714 let buf = make_full_message(PROC_EVENT_EXEC, &data);
715 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
716 assert_eq!(
717 event,
718 ProcEvent::Exec {
719 pid: 42,
720 tgid: 100,
721 }
722 );
723 }
724
725 #[test]
726 fn parse_fork() {
727 let data = [
728 10i32.to_ne_bytes(), 10i32.to_ne_bytes(), 20i32.to_ne_bytes(), 20i32.to_ne_bytes(), ]
733 .concat();
734 let buf = make_full_message(PROC_EVENT_FORK, &data);
735 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
736 assert_eq!(
737 event,
738 ProcEvent::Fork {
739 parent_pid: 10,
740 parent_tgid: 10,
741 child_pid: 20,
742 child_tgid: 20,
743 }
744 );
745 }
746
747 #[test]
748 fn parse_exit() {
749 let data = [
750 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 0u32.to_ne_bytes(), 17u32.to_ne_bytes(), 0i32.to_ne_bytes(), 0i32.to_ne_bytes(), ]
757 .concat();
758 let buf = make_full_message(PROC_EVENT_EXIT, &data);
759 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
760 assert_eq!(
761 event,
762 ProcEvent::Exit {
763 pid: 1,
764 tgid: 1,
765 exit_code: 0,
766 exit_signal: 17,
767 }
768 );
769 }
770
771 #[test]
772 fn parse_uid() {
773 let data = [
774 5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 1000u32.to_ne_bytes(), 0u32.to_ne_bytes(), ]
779 .concat();
780 let buf = make_full_message(PROC_EVENT_UID, &data);
781 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
782 assert_eq!(
783 event,
784 ProcEvent::Uid {
785 pid: 5,
786 tgid: 5,
787 ruid: 1000,
788 euid: 0,
789 }
790 );
791 }
792
793 #[test]
794 fn parse_gid() {
795 let data = [
796 5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 100u32.to_ne_bytes(), 200u32.to_ne_bytes(), ]
801 .concat();
802 let buf = make_full_message(PROC_EVENT_GID, &data);
803 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
804 assert_eq!(
805 event,
806 ProcEvent::Gid {
807 pid: 5,
808 tgid: 5,
809 rgid: 100,
810 egid: 200,
811 }
812 );
813 }
814
815 #[test]
816 fn parse_sid() {
817 let data = [
818 7i32.to_ne_bytes(), 7i32.to_ne_bytes(), ]
821 .concat();
822 let buf = make_full_message(PROC_EVENT_SID, &data);
823 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
824 assert_eq!(
825 event,
826 ProcEvent::Sid { pid: 7, tgid: 7 }
827 );
828 }
829
830 #[test]
831 fn parse_ptrace() {
832 let data = [
833 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 999i32.to_ne_bytes(), 999i32.to_ne_bytes(), ]
838 .concat();
839 let buf = make_full_message(PROC_EVENT_PTRACE, &data);
840 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
841 assert_eq!(
842 event,
843 ProcEvent::Ptrace {
844 pid: 1,
845 tgid: 1,
846 tracer_pid: 999,
847 tracer_tgid: 999,
848 }
849 );
850 }
851
852 #[test]
853 fn parse_comm() {
854 let data = [
855 42i32.to_ne_bytes(), 42i32.to_ne_bytes(), ]
858 .concat();
859 let mut comm_event = data;
860 let mut comm = [0u8; 16];
861 comm[..7].copy_from_slice(b"bash\0\0\0");
862 comm_event.extend_from_slice(&comm);
863
864 let buf = make_full_message(PROC_EVENT_COMM, &comm_event);
865 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
866 assert_eq!(
867 event,
868 ProcEvent::Comm {
869 pid: 42,
870 tgid: 42,
871 comm,
872 }
873 );
874 }
875
876 #[test]
877 fn parse_coredump() {
878 let data = [
879 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 0i32.to_ne_bytes(), 0i32.to_ne_bytes(), ]
884 .concat();
885 let buf = make_full_message(PROC_EVENT_COREDUMP, &data);
886 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
887 assert_eq!(
888 event,
889 ProcEvent::Coredump { pid: 1, tgid: 1 }
890 );
891 }
892
893 #[test]
894 fn parse_unknown_skipped() {
895 let data = [1u8, 2, 3, 4];
896 let buf = make_full_message(0xDEAD, &data);
897 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
898 match event {
899 ProcEvent::Unknown { what, raw_data } => {
900 assert_eq!(what, 0xDEAD);
901 assert_eq!(raw_data, data);
902 }
903 _ => panic!("expected Unknown event"),
904 }
905 }
906
907 #[test]
908 fn parse_nlmsg_noop() {
909 let buf = make_netlink_message(NLMSG_NOOP, &[]);
910 let result = parse_netlink_message(&buf, buf.len()).unwrap();
911 assert!(result.is_none());
912 }
913
914 #[test]
915 fn parse_nlmsg_done() {
916 let buf = make_netlink_message(NLMSG_DONE, &[]);
917 let result = parse_netlink_message(&buf, buf.len()).unwrap();
918 assert!(result.is_none());
919 }
920
921 #[test]
922 fn parse_nlmsg_error() {
923 let errno = -libc::EPERM;
925 let mut payload = vec![0u8; SIZE_NLMSGHDR + 20]; payload[0..4].copy_from_slice(&errno.to_ne_bytes());
927
928 let buf = make_netlink_message(NLMSG_ERROR, &payload);
929 let result = parse_netlink_message(&buf, buf.len());
930 assert!(result.is_err());
931 match result {
932 Err(Error::Os(e)) => assert_eq!(e.raw_os_error(), Some(libc::EPERM)),
933 _ => panic!("expected Os error"),
934 }
935 }
936
937 #[test]
938 fn parse_nlmsg_overrun() {
939 let buf = make_netlink_message(NLMSG_OVERRUN, &[]);
940 let result = parse_netlink_message(&buf, buf.len());
941 match result {
942 Err(Error::Overrun) => {} _ => panic!("expected Overrun error"),
944 }
945 }
946
947 #[test]
948 fn truncated_message() {
949 let buf = vec![0u8; 4]; let result = parse_netlink_message(&buf, buf.len());
951 match result {
952 Err(Error::Truncated) => {} _ => panic!("expected Truncated error"),
954 }
955 }
956
957 #[test]
958 fn display_exec() {
959 let event = ProcEvent::Exec {
960 pid: 42,
961 tgid: 100,
962 };
963 assert_eq!(format!("{event}"), "EXEC pid=42 tgid=100");
964 }
965
966 #[test]
967 fn display_comm() {
968 let mut comm = [0u8; 16];
969 comm[..4].copy_from_slice(b"bash");
970 let event = ProcEvent::Comm {
971 pid: 1,
972 tgid: 1,
973 comm,
974 };
975 assert_eq!(format!("{event}"), "COMM pid=1 tgid=1 name=\"bash\"");
976 }
977
978
979
980 #[test]
981 fn multi_part_message_iteration() {
982 let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
984 let msg1 = make_full_message(PROC_EVENT_EXEC, &exec_data);
985
986 let exec_data2 = [43i32.to_ne_bytes(), 101i32.to_ne_bytes()].concat();
987 let msg2 = make_full_message(PROC_EVENT_EXEC, &exec_data2);
988
989 let mut combined = Vec::new();
990 combined.extend_from_slice(&msg1);
991 combined.extend_from_slice(&msg2);
992
993 let iter = NetlinkMessageIter::new(&combined, combined.len());
994 let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
995 assert_eq!(events.len(), 2);
996 assert_eq!(events[0], ProcEvent::Exec { pid: 42, tgid: 100 });
997 assert_eq!(events[1], ProcEvent::Exec { pid: 43, tgid: 101 });
998 }
999
1000 #[test]
1001 fn test_nlmsg_hdrlen() {
1002 assert_eq!(nlmsg_hdrlen(), 16);
1004 }
1005
1006 #[test]
1007 fn test_nlmsg_align() {
1008 assert_eq!(nlmsg_align(0), 0);
1009 assert_eq!(nlmsg_align(1), 4);
1010 assert_eq!(nlmsg_align(4), 4);
1011 assert_eq!(nlmsg_align(5), 8);
1012 assert_eq!(nlmsg_align(16), 16);
1013 }
1014
1015 #[test]
1016 fn test_nlmsg_length() {
1017 assert_eq!(nlmsg_length(0), 16);
1018 assert_eq!(nlmsg_length(20), 36);
1019 }
1020
1021 #[test]
1026 fn recv_buffer_too_small() {
1027 let err = Error::BufferTooSmall {
1033 needed: SIZE_NLMSGHDR,
1034 };
1035 assert_eq!(
1036 format!("{err}"),
1037 "buffer too small, need at least 16 bytes"
1038 );
1039 }
1040
1041 #[test]
1046 fn parse_exec_truncated() {
1047 let data = [42i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_EXEC, &data);
1049 let result = parse_netlink_message(&buf, buf.len());
1050 assert!(matches!(result, Err(Error::Truncated)));
1051 }
1052
1053 #[test]
1054 fn parse_fork_truncated() {
1055 let data = [
1056 10i32.to_ne_bytes(), 10i32.to_ne_bytes(), 20i32.to_ne_bytes(), ]
1061 .concat();
1062 let buf = make_full_message(PROC_EVENT_FORK, &data);
1063 let result = parse_netlink_message(&buf, buf.len());
1064 assert!(matches!(result, Err(Error::Truncated)));
1065 }
1066
1067 #[test]
1068 fn parse_exit_truncated() {
1069 let data = [
1070 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 0u32.to_ne_bytes(), 17u32.to_ne_bytes(), 0i32.to_ne_bytes(), ]
1077 .concat();
1078 let buf = make_full_message(PROC_EVENT_EXIT, &data);
1079 let result = parse_netlink_message(&buf, buf.len());
1080 assert!(matches!(result, Err(Error::Truncated)));
1081 }
1082
1083 #[test]
1084 fn parse_uid_truncated() {
1085 let data = [
1086 5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 1000u32.to_ne_bytes(), ]
1091 .concat();
1092 let buf = make_full_message(PROC_EVENT_UID, &data);
1093 let result = parse_netlink_message(&buf, buf.len());
1094 assert!(matches!(result, Err(Error::Truncated)));
1095 }
1096
1097 #[test]
1098 fn parse_gid_truncated() {
1099 let data = [
1100 5i32.to_ne_bytes(), 5i32.to_ne_bytes(), 100u32.to_ne_bytes(), ]
1105 .concat();
1106 let buf = make_full_message(PROC_EVENT_GID, &data);
1107 let result = parse_netlink_message(&buf, buf.len());
1108 assert!(matches!(result, Err(Error::Truncated)));
1109 }
1110
1111 #[test]
1112 fn parse_sid_truncated() {
1113 let data = [7i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_SID, &data);
1115 let result = parse_netlink_message(&buf, buf.len());
1116 assert!(matches!(result, Err(Error::Truncated)));
1117 }
1118
1119 #[test]
1120 fn parse_ptrace_truncated() {
1121 let data = [
1122 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), 999i32.to_ne_bytes(), ]
1127 .concat();
1128 let buf = make_full_message(PROC_EVENT_PTRACE, &data);
1129 let result = parse_netlink_message(&buf, buf.len());
1130 assert!(matches!(result, Err(Error::Truncated)));
1131 }
1132
1133 #[test]
1134 fn parse_comm_truncated_missing_comm() {
1135 let data = [42i32.to_ne_bytes(), 42i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_COMM, &data);
1139 let result = parse_netlink_message(&buf, buf.len());
1140 assert!(matches!(result, Err(Error::Truncated)));
1141 }
1142
1143 #[test]
1144 fn parse_coredump_truncated() {
1145 let data = [1i32.to_ne_bytes()].concat(); let buf = make_full_message(PROC_EVENT_COREDUMP, &data);
1147 let result = parse_netlink_message(&buf, buf.len());
1148 assert!(matches!(result, Err(Error::Truncated)));
1149 }
1150
1151 #[test]
1156 fn parse_nlmsg_len_too_small() {
1157 let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 20]);
1159 buf[0..4].copy_from_slice(&10u32.to_ne_bytes()); let result = parse_netlink_message(&buf, buf.len());
1161 assert!(matches!(result, Err(Error::Truncated)));
1162 }
1163
1164 #[test]
1165 fn parse_nlmsg_len_exceeds_buffer() {
1166 let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 20]);
1168 buf[0..4].copy_from_slice(&1000u32.to_ne_bytes()); let result = parse_netlink_message(&buf, buf.len());
1170 assert!(matches!(result, Err(Error::Truncated)));
1171 }
1172
1173 #[test]
1174 fn parse_cn_msg_wrong_idx() {
1175 let proc_payload = make_proc_event_payload(PROC_EVENT_EXEC, &[42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat());
1177
1178 let mut cn_payload = Vec::with_capacity(SIZE_CN_MSG + proc_payload.len());
1180 cn_payload.extend_from_slice(&999u32.to_ne_bytes()); cn_payload.extend_from_slice(&CN_VAL_PROC.to_ne_bytes());
1182 cn_payload.extend_from_slice(&0u32.to_ne_bytes());
1183 cn_payload.extend_from_slice(&0u32.to_ne_bytes());
1184 cn_payload.extend_from_slice(&(proc_payload.len() as u16).to_ne_bytes());
1185 cn_payload.extend_from_slice(&0u16.to_ne_bytes());
1186 cn_payload.extend_from_slice(&proc_payload);
1187
1188 let buf = make_netlink_message(NLMSG_MIN_TYPE, &cn_payload);
1189 let result = parse_netlink_message(&buf, buf.len());
1190 assert!(matches!(result, Err(Error::Truncated)));
1191 }
1192
1193 #[test]
1194 fn parse_cn_msg_truncated_header() {
1195 let buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 10]);
1197 let result = parse_netlink_message(&buf, buf.len());
1198 assert!(matches!(result, Err(Error::Truncated)));
1199 }
1200
1201 #[test]
1202 fn parse_nlmsg_error_ack() {
1203 let mut payload = vec![0u8; 20];
1205 payload[0..4].copy_from_slice(&0i32.to_ne_bytes()); let buf = make_netlink_message(NLMSG_ERROR, &payload);
1207 let result = parse_netlink_message(&buf, buf.len()).unwrap();
1208 assert!(result.is_none());
1209 }
1210
1211 #[test]
1216 fn parse_exec_negative_pid() {
1217 let data = [(-1i32).to_ne_bytes(), (-1i32).to_ne_bytes()].concat();
1219 let buf = make_full_message(PROC_EVENT_EXEC, &data);
1220 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
1221 assert_eq!(
1223 event,
1224 ProcEvent::Exec {
1225 pid: u32::MAX,
1226 tgid: u32::MAX,
1227 }
1228 );
1229 }
1230
1231 #[test]
1232 fn parse_comm_no_nul() {
1233 let data = [
1235 42i32.to_ne_bytes(), 42i32.to_ne_bytes(), ]
1238 .concat();
1239 let mut comm_event = data;
1240 let comm: [u8; 16] = [
1241 b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h',
1242 b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
1243 ];
1244 comm_event.extend_from_slice(&comm);
1245
1246 let buf = make_full_message(PROC_EVENT_COMM, &comm_event);
1247 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
1248 assert_eq!(
1249 event,
1250 ProcEvent::Comm {
1251 pid: 42,
1252 tgid: 42,
1253 comm,
1254 }
1255 );
1256 let s = format!("{event}");
1258 assert_eq!(s, "COMM pid=42 tgid=42 name=\"abcdefghijklmnop\"");
1259 }
1260
1261 #[test]
1262 fn parse_comm_invalid_utf8() {
1263 let data = [
1265 1i32.to_ne_bytes(), 1i32.to_ne_bytes(), ]
1268 .concat();
1269 let mut comm_event = data;
1270 let mut comm = [0u8; 16];
1271 comm[0] = 0xFF; comm[1] = 0xFE;
1273 comm[2] = 0;
1274 comm_event.extend_from_slice(&comm);
1275
1276 let buf = make_full_message(PROC_EVENT_COMM, &comm_event);
1277 let event = format!(
1278 "{}",
1279 parse_netlink_message(&buf, buf.len()).unwrap().unwrap()
1280 );
1281 assert!(event.contains("<invalid>"));
1282 }
1283
1284 #[test]
1285 fn parse_unknown_large_data_skipped() {
1286 let data = vec![0xABu8; 1024];
1287 let buf = make_full_message(0xFFFFFFFF, &data);
1288 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
1289 match event {
1290 ProcEvent::Unknown { what, raw_data } => {
1291 assert_eq!(what, 0xFFFFFFFF);
1292 assert_eq!(raw_data.len(), 1024);
1293 assert_eq!(raw_data[0], 0xAB);
1294 assert_eq!(raw_data[1023], 0xAB);
1295 }
1296 _ => panic!("expected Unknown"),
1297 }
1298 }
1299
1300 #[test]
1301 fn parse_zero_length_proc_event_data() {
1302 let proc_payload = make_proc_event_payload(PROC_EVENT_EXEC, &[]);
1304 let cn_payload = make_cn_msg(&proc_payload);
1305 let buf = make_netlink_message(NLMSG_MIN_TYPE, &cn_payload);
1306 let result = parse_netlink_message(&buf, buf.len());
1307 assert!(matches!(result, Err(Error::Truncated)));
1308 }
1309
1310 #[test]
1315 fn display_fork() {
1316 let event = ProcEvent::Fork {
1317 parent_pid: 100,
1318 parent_tgid: 100,
1319 child_pid: 200,
1320 child_tgid: 200,
1321 };
1322 assert_eq!(
1323 format!("{event}"),
1324 "FORK parent=(100,100) child=(200,200)"
1325 );
1326 }
1327
1328 #[test]
1329 fn display_exit() {
1330 let event = ProcEvent::Exit {
1331 pid: 42,
1332 tgid: 42,
1333 exit_code: 0,
1334 exit_signal: 17,
1335 };
1336 assert_eq!(format!("{event}"), "EXIT pid=42 tgid=42 code=0 signal=17");
1337 }
1338
1339 #[test]
1340 fn display_uid() {
1341 let event = ProcEvent::Uid {
1342 pid: 1,
1343 tgid: 1,
1344 ruid: 1000,
1345 euid: 0,
1346 };
1347 assert_eq!(format!("{event}"), "UID pid=1 tgid=1 ruid=1000 euid=0");
1348 }
1349
1350 #[test]
1351 fn display_gid() {
1352 let event = ProcEvent::Gid {
1353 pid: 2,
1354 tgid: 2,
1355 rgid: 100,
1356 egid: 200,
1357 };
1358 assert_eq!(format!("{event}"), "GID pid=2 tgid=2 rgid=100 egid=200");
1359 }
1360
1361 #[test]
1362 fn display_sid() {
1363 let event = ProcEvent::Sid { pid: 3, tgid: 3 };
1364 assert_eq!(format!("{event}"), "SID pid=3 tgid=3");
1365 }
1366
1367 #[test]
1368 fn display_ptrace() {
1369 let event = ProcEvent::Ptrace {
1370 pid: 10,
1371 tgid: 10,
1372 tracer_pid: 99,
1373 tracer_tgid: 99,
1374 };
1375 assert_eq!(
1376 format!("{event}"),
1377 "PTRACE pid=10 tgid=10 tracer=(99,99)"
1378 );
1379 }
1380
1381 #[test]
1382 fn display_coredump() {
1383 let event = ProcEvent::Coredump { pid: 7, tgid: 7 };
1384 assert_eq!(format!("{event}"), "COREDUMP pid=7 tgid=7");
1385 }
1386
1387 #[test]
1392 fn iter_empty_buffer() {
1393 let iter = NetlinkMessageIter::new(&[], 0);
1394 assert_eq!(iter.count(), 0);
1395 }
1396
1397 #[test]
1398 fn iter_single_done_message() {
1399 let buf = make_netlink_message(NLMSG_DONE, &[]);
1400 let iter = NetlinkMessageIter::new(&buf, buf.len());
1401 let results: Vec<_> = iter.collect();
1402 assert_eq!(results.len(), 0);
1404 }
1405
1406 #[test]
1407 fn iter_done_terminates_early() {
1408 let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
1410 let msg_exec = make_full_message(PROC_EVENT_EXEC, &exec_data);
1411 let msg_done = make_netlink_message(NLMSG_DONE, &[]);
1412
1413 let mut combined = Vec::new();
1414 combined.extend_from_slice(&msg_exec);
1415 combined.extend_from_slice(&msg_done);
1416
1417 let iter = NetlinkMessageIter::new(&combined, combined.len());
1418 let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
1419 assert_eq!(events.len(), 1);
1420 assert_eq!(events[0], ProcEvent::Exec { pid: 42, tgid: 100 });
1421 }
1422
1423 #[test]
1424 fn iter_interleaved_control_messages() {
1425 let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
1427 let fork_data = [
1428 10i32.to_ne_bytes(),
1429 10i32.to_ne_bytes(),
1430 20i32.to_ne_bytes(),
1431 20i32.to_ne_bytes(),
1432 ]
1433 .concat();
1434
1435 let msg_noop = make_netlink_message(NLMSG_NOOP, &[]);
1436 let msg_exec = make_full_message(PROC_EVENT_EXEC, &exec_data);
1437 let msg_fork = make_full_message(PROC_EVENT_FORK, &fork_data);
1438 let msg_done = make_netlink_message(NLMSG_DONE, &[]);
1439
1440 let mut combined = Vec::new();
1441 combined.extend_from_slice(&msg_noop);
1442 combined.extend_from_slice(&msg_exec);
1443 combined.extend_from_slice(&msg_noop);
1444 combined.extend_from_slice(&msg_fork);
1445 combined.extend_from_slice(&msg_done);
1446
1447 let iter = NetlinkMessageIter::new(&combined, combined.len());
1448 let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
1449 assert_eq!(events.len(), 2);
1450 assert_eq!(events[0], ProcEvent::Exec { pid: 42, tgid: 100 });
1451 assert_eq!(
1452 events[1],
1453 ProcEvent::Fork {
1454 parent_pid: 10,
1455 parent_tgid: 10,
1456 child_pid: 20,
1457 child_tgid: 20,
1458 }
1459 );
1460 }
1461
1462 #[test]
1463 fn iter_malformed_zero_length() {
1464 let mut buf = vec![0u8; 16];
1466 buf[0..4].copy_from_slice(&0u32.to_ne_bytes()); let mut iter = NetlinkMessageIter::new(&buf, buf.len());
1468 let result = iter.next().unwrap();
1469 assert!(matches!(result, Err(Error::Truncated)));
1470 }
1471
1472 #[test]
1473 fn iter_remaining_too_small_for_header() {
1474 let buf = vec![0u8; 4];
1476 let mut iter = NetlinkMessageIter::new(&buf, 4);
1477 let result = iter.next().unwrap();
1478 assert!(matches!(result, Err(Error::Truncated)));
1479 }
1480
1481 #[test]
1482 fn iter_no_valid_msgs_returns_none_on_second_call() {
1483 let buf = make_netlink_message(NLMSG_DONE, &[]);
1484 let mut iter = NetlinkMessageIter::new(&buf, buf.len());
1485 assert!(iter.next().is_none());
1487 assert!(iter.next().is_none());
1489 }
1490
1491 #[test]
1496 fn test_nlmsg_align_max() {
1497 assert_eq!(nlmsg_align(65535), 65536); assert_eq!(nlmsg_align(65536), 65536);
1500 assert_eq!(nlmsg_align(65537), 65540);
1501 }
1502
1503 #[test]
1504 fn test_nlmsg_align_neg_like() {
1505 assert_eq!(nlmsg_align(2), 4);
1507 assert_eq!(nlmsg_align(3), 4);
1508 assert_eq!(nlmsg_align(6), 8);
1509 assert_eq!(nlmsg_align(7), 8);
1510 assert_eq!(nlmsg_align(8), 8);
1511 assert_eq!(nlmsg_align(9), 12);
1512 }
1513
1514 #[test]
1519 fn parse_cn_msg_truncated_no_data() {
1520 let result = parse_cn_msg(&[0u8; 15]);
1522 assert!(matches!(result, Err(Error::Truncated)));
1523 }
1524
1525 #[test]
1526 fn parse_cn_msg_data_len_mismatch() {
1527 let mut buf = vec![0u8; SIZE_CN_MSG + 4];
1529 buf[0..4].copy_from_slice(&CN_IDX_PROC.to_ne_bytes());
1530 buf[4..8].copy_from_slice(&CN_VAL_PROC.to_ne_bytes());
1531 buf[16..18].copy_from_slice(&100u16.to_ne_bytes()); let result = parse_cn_msg(&buf);
1534 assert!(matches!(result, Err(Error::Truncated)));
1535 }
1536
1537 #[test]
1542 fn iter_many_messages() {
1543 let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
1544 let mut combined = Vec::new();
1545
1546 for _ in 0..100 {
1547 let msg = make_full_message(PROC_EVENT_EXEC, &exec_data);
1548 combined.extend_from_slice(&msg);
1549 }
1550
1551 let iter = NetlinkMessageIter::new(&combined, combined.len());
1552 let count = iter.filter_map(|r| r.ok().flatten()).count();
1553 assert_eq!(count, 100);
1554 }
1555
1556 #[test]
1561 fn error_display_all_variants() {
1562 assert_eq!(
1563 format!("{}", Error::Os(std::io::Error::from_raw_os_error(1))),
1564 "system call error: Operation not permitted (os error 1)"
1565 );
1566 assert_eq!(format!("{}", Error::Truncated), "truncated message");
1567 assert_eq!(
1568 format!("{}", Error::BufferTooSmall { needed: 64 }),
1569 "buffer too small, need at least 64 bytes"
1570 );
1571 assert_eq!(
1572 format!("{}", Error::Interrupted),
1573 "interrupted by signal"
1574 );
1575 assert_eq!(
1576 format!("{}", Error::ConnectionClosed),
1577 "connection closed"
1578 );
1579 assert_eq!(
1580 format!("{}", Error::Overrun),
1581 "message overrun, events may have been dropped"
1582 );
1583 }
1584
1585 #[test]
1586 fn error_source() {
1587 use std::error::Error as _;
1588 assert!(Error::Os(std::io::Error::from_raw_os_error(1)).source().is_some());
1589 assert!(Error::Truncated.source().is_none());
1590 assert!(Error::BufferTooSmall { needed: 16 }.source().is_none());
1591 assert!(Error::Interrupted.source().is_none());
1592 assert!(Error::ConnectionClosed.source().is_none());
1593 assert!(Error::Overrun.source().is_none());
1594 }
1595
1596 #[test]
1601 fn parse_nlmsg_len_inconsistent() {
1602 let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &[0u8; 100]);
1605 buf[0..4].copy_from_slice(&20u32.to_ne_bytes()); let result = parse_netlink_message(&buf, buf.len());
1608 assert!(matches!(result, Err(Error::Truncated)));
1612 }
1613
1614 #[test]
1619 fn proc_event_clone_and_eq() {
1620 let e1 = ProcEvent::Exec {
1621 pid: 42,
1622 tgid: 100,
1623 };
1624 let e2 = e1.clone();
1625 assert_eq!(e1, e2);
1626
1627 let e3 = ProcEvent::Exec {
1628 pid: 43,
1629 tgid: 100,
1630 };
1631 assert_ne!(e1, e3);
1632 }
1633
1634 #[test]
1639 fn roundtrip_all_event_types() {
1640 use std::error::Error as _;
1646 let _ = Error::Truncated.source(); }
1648
1649 #[test]
1654 fn parse_with_nlm_f_request_flag() {
1655 let exec_data = [42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat();
1657 let proc_payload = make_proc_event_payload(PROC_EVENT_EXEC, &exec_data);
1658 let cn_payload = make_cn_msg(&proc_payload);
1659 let mut buf = make_netlink_message(NLMSG_MIN_TYPE, &cn_payload);
1660 buf[6..8].copy_from_slice(&NLM_F_REQUEST.to_ne_bytes());
1661
1662 let event = parse_netlink_message(&buf, buf.len()).unwrap().unwrap();
1663 assert_eq!(event, ProcEvent::Exec { pid: 42, tgid: 100 });
1664 }
1665
1666 #[test]
1671 fn iter_alignment_correct() {
1672 let msg1 = make_netlink_message(NLMSG_NOOP, &[]); let msg2 = make_full_message(PROC_EVENT_EXEC, &[42i32.to_ne_bytes(), 100i32.to_ne_bytes()].concat());
1675 let msg3 = make_netlink_message(NLMSG_DONE, &[]);
1676
1677 let mut combined = Vec::new();
1678 combined.extend_from_slice(&msg1);
1679 combined.extend_from_slice(&msg2);
1680 combined.extend_from_slice(&msg3);
1681
1682 let mut pos = 0;
1684
1685 let len1 = u32::from_ne_bytes(msg1[0..4].try_into().unwrap()) as usize;
1687 assert_eq!(len1, 16);
1688 pos += nlmsg_align(len1);
1689
1690 let len2 = u32::from_ne_bytes(msg2[0..4].try_into().unwrap()) as usize;
1692 assert!(len2 > 16);
1693 assert_eq!(pos, 16); pos += nlmsg_align(len2);
1695
1696 let len3 = u32::from_ne_bytes(msg3[0..4].try_into().unwrap()) as usize;
1698 assert_eq!(len3, 16);
1699 assert_eq!(pos, 16 + nlmsg_align(len2)); let iter = NetlinkMessageIter::new(&combined, combined.len());
1703 let events: Vec<_> = iter.filter_map(|r| r.ok().flatten()).collect();
1704 assert_eq!(events.len(), 1);
1705 assert_eq!(events[0], ProcEvent::Exec { pid: 42, tgid: 100 });
1706 }
1707}
1708