diff --git a/aya/src/interface.rs b/aya/src/interface.rs new file mode 100644 index 00000000..8e204a3a --- /dev/null +++ b/aya/src/interface.rs @@ -0,0 +1,168 @@ +//! Network interfaces representing kernel network interfaces. + +use std::ffi::CStr; + +use libc::if_nameindex; + +#[derive(Debug, Clone)] +/// A kernel network interface. +// if_name isn't stored because it can change in the kernel and aya won't know +pub struct NetworkInterface { + pub(crate) index: i32, +} + +impl NetworkInterface { + /// Provides a number that can be used to identify this interface on this system. + pub fn index(&self) -> i32 { + self.index + } + + /// Extracts the interface name from the kernel. + pub fn name(&self) -> Result { + let mut buffer: [libc::c_char; libc::IF_NAMESIZE] = [0; libc::IF_NAMESIZE]; + let name = unsafe { + // Returns null on error + let res = libc::if_indextoname(self.index as u32, buffer.as_mut_ptr()); + + if res.is_null() { + return Err(std::io::Error::last_os_error()); + } + + CStr::from_ptr(buffer.as_ptr()) + }; + + Ok(name.to_string_lossy().to_string()) + } + + /// Provides a [Vec] of all operating system network interfaces, including virtual ones. + /// # Example + /// + /// ``` + /// let interfaces_names: Vec = NetworkInterface::list() + /// .iter() + /// .map(|interface| interface.name().unwrap()) + /// .collect(); + /// ``` + pub fn list() -> Vec { + let mut list = Vec::new(); + + // The nameindex array is terminated by an interface with if_index == 0 and if_name == null + let head = unsafe { libc::if_nameindex() }; + let mut curr = head; + + while let Ok(interface) = NetworkInterface::try_from(unsafe { *curr }) { + list.push(interface); + curr = unsafe { curr.add(1) }; + } + + unsafe { + libc::if_freenameindex(head); + }; + + list + } +} + +impl TryFrom for NetworkInterface { + type Error = (); + + // Returns Err is the interface is invalid (zeroed) + fn try_from(value: if_nameindex) -> Result { + if value.if_index == 0 || value.if_name.is_null() { + return Err(()); + } + + Ok(NetworkInterface { + index: value.if_index as i32, + }) + } +} + +#[cfg(test)] +mod tests { + use std::ffi::CString; + + use crate::interface::NetworkInterface; + + #[test] + fn network_interface_list() { + let interfaces_dir = "/sys/class/net"; + + let expected: Vec = std::fs::read_dir(interfaces_dir) + .unwrap() + .map(|entry| entry.unwrap().file_name().into_string().unwrap()) + .collect(); + + let interfaces = NetworkInterface::list(); + + assert_eq!(expected.len(), interfaces.len()); + + for interface in interfaces { + let name = interface.name().unwrap().to_string(); + assert!(expected.contains(&name)); + } + } + + #[test] + fn network_interface_try_from() { + use libc::if_nameindex; + use std::ptr::null_mut; + + let name = CString::new("eth0").unwrap(); + + let k_interface = if_nameindex { + if_index: 1, + if_name: name.as_ptr() as *mut i8, + }; + + let interface = NetworkInterface::try_from(k_interface).unwrap(); + + assert_eq!(interface.index(), 1); + + let invalid_k_interface = if_nameindex { + if_index: 0, + if_name: null_mut(), + }; + + let res = NetworkInterface::try_from(invalid_k_interface); + assert_eq!(res.unwrap_err(), ()); + + let invalid_k_interface = if_nameindex { + if_index: 1, + if_name: null_mut(), + }; + + let res = NetworkInterface::try_from(invalid_k_interface); + assert_eq!(res.unwrap_err(), ()); + } + + #[test] + fn network_interface_name() { + let interfaces_dir = "/sys/class/net"; + + let first_interface_path = std::fs::read_dir(interfaces_dir) + .expect("Failed to read sysfs interface directory") + .next(); + + if let Some(first_interface_path) = first_interface_path { + let (name, index) = { + let entry = first_interface_path.unwrap(); + let file_name = entry.file_name(); + let mut path = entry.path(); + path.push("ifindex"); + let index_contents = String::from_utf8(std::fs::read(path).unwrap()).unwrap(); + let index = index_contents.trim().parse::().unwrap(); + (file_name, index) + }; + + let interface = NetworkInterface { index }; + + assert_eq!( + name.to_string_lossy().to_string(), + interface.name().unwrap() + ); + } else { + panic!("no interfaces found in {interfaces_dir} to test"); + } + } +} diff --git a/aya/src/lib.rs b/aya/src/lib.rs index 14f3eb2d..3bcf1546 100644 --- a/aya/src/lib.rs +++ b/aya/src/lib.rs @@ -47,6 +47,7 @@ extern crate bitflags; mod bpf; mod generated; +pub mod interface; pub mod maps; mod obj; pub mod pin; diff --git a/aya/src/programs/links.rs b/aya/src/programs/links.rs index 58d0ed12..42e12524 100644 --- a/aya/src/programs/links.rs +++ b/aya/src/programs/links.rs @@ -3,7 +3,7 @@ use libc::{close, dup}; use thiserror::Error; use std::{ - collections::{hash_map::Entry, HashMap}, + collections::{hash_map::{Entry, self}, HashMap}, ffi::CString, io, os::unix::prelude::RawFd, @@ -69,6 +69,10 @@ impl LinkMap { pub(crate) fn forget(&mut self, link_id: T::Id) -> Result { self.links.remove(&link_id).ok_or(ProgramError::NotAttached) } + + pub(crate) fn iter(&self) -> hash_map::Values<'_, T::Id, T> { + self.links.values() + } } impl Drop for LinkMap { @@ -314,7 +318,7 @@ mod tests { #[derive(Debug, Hash, Eq, PartialEq)] struct TestLinkId(u8, u8); - #[derive(Debug)] + #[derive(Clone, Debug, Eq, PartialEq)] struct TestLink { id: (u8, u8), detached: Rc>, @@ -365,6 +369,26 @@ mod tests { assert!(*l2_detached.borrow() == 1); } + #[test] + fn test_iter() { + let mut links = LinkMap::new(); + let l1 = TestLink::new(1, 2); + let l2 = TestLink::new(1, 3); + + let l1_copy = l1.clone(); + let l2_copy = l2.clone(); + + let _id1 = links.insert(l1).unwrap(); + let _id2 = links.insert(l2).unwrap(); + + assert_eq!(links.iter().len(), 2); + + let collected: Vec<&TestLink> = links.iter().collect(); + let expected: Vec<&TestLink> = vec![&l1_copy, &l2_copy]; + + assert_eq!(collected, expected); + } + #[test] fn test_already_attached() { let mut links = LinkMap::new(); diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index a2a45969..996a9c26 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -73,34 +73,38 @@ use std::{ }; use thiserror::Error; -pub use cgroup_device::CgroupDevice; -pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType}; -pub use cgroup_sock::{CgroupSock, CgroupSockAttachType}; -pub use cgroup_sock_addr::{CgroupSockAddr, CgroupSockAddrAttachType}; -pub use cgroup_sockopt::{CgroupSockopt, CgroupSockoptAttachType}; -pub use cgroup_sysctl::CgroupSysctl; -pub use extension::{Extension, ExtensionError}; -pub use fentry::FEntry; -pub use fexit::FExit; -pub use kprobe::{KProbe, KProbeError}; +pub use cgroup_device::{CgroupDevice, CgroupDeviceLink}; +pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType, CgroupSkbLink}; +pub use cgroup_sock::{CgroupSock, CgroupSockAttachType, CgroupSockLink}; +pub use cgroup_sock_addr::{ + CgroupSockAddr, CgroupSockAddrAttachType, CgroupSockAddrLink, CgroupSockAddrLinkId, +}; +pub use cgroup_sockopt::{ + CgroupSockopt, CgroupSockoptAttachType, CgroupSockoptLink, CgroupSockoptLinkId, +}; +pub use cgroup_sysctl::{CgroupSysctl, CgroupSysctlLink}; +pub use extension::{Extension, ExtensionError, ExtensionLink}; +pub use fentry::{FEntry, FEntryLink}; +pub use fexit::{FExit, FExitLink}; +pub use kprobe::{KProbe, KProbeError, KProbeLink}; pub use links::Link; use links::*; -pub use lirc_mode2::LircMode2; -pub use lsm::Lsm; +pub use lirc_mode2::{LircLink, LircLinkId, LircMode2}; +pub use lsm::{Lsm, LsmLink}; use perf_attach::*; pub use perf_event::{PerfEvent, PerfEventScope, PerfTypeId, SamplePolicy}; pub use probe::ProbeKind; -pub use raw_trace_point::RawTracePoint; -pub use sk_lookup::SkLookup; -pub use sk_msg::SkMsg; -pub use sk_skb::{SkSkb, SkSkbKind}; -pub use sock_ops::SockOps; -pub use socket_filter::{SocketFilter, SocketFilterError}; -pub use tc::{SchedClassifier, TcAttachType, TcError}; -pub use tp_btf::BtfTracePoint; -pub use trace_point::{TracePoint, TracePointError}; -pub use uprobe::{UProbe, UProbeError}; -pub use xdp::{Xdp, XdpError, XdpFlags}; +pub use raw_trace_point::{RawTracePoint, RawTracePointLink}; +pub use sk_lookup::{SkLookup, SkLookupLink}; +pub use sk_msg::{SkMsg, SkMsgLink}; +pub use sk_skb::{SkSkb, SkSkbKind, SkSkbLink}; +pub use sock_ops::{SockOps, SockOpsLink}; +pub use socket_filter::{SocketFilter, SocketFilterError, SocketFilterLink}; +pub use tc::{SchedClassifier, SchedClassifierLink, SchedClassifierLinkId, TcAttachType, TcError}; +pub use tp_btf::{BtfTracePoint, BtfTracePointLink}; +pub use trace_point::{TracePoint, TracePointError, TracePointLink}; +pub use uprobe::{UProbe, UProbeError, UProbeLink}; +pub use xdp::{Xdp, XdpError, XdpFlags, XdpLink}; use crate::{ generated::{bpf_attach_type, bpf_prog_info, bpf_prog_type}, @@ -688,6 +692,48 @@ impl_fd!( CgroupDevice, ); +macro_rules! impl_program_links{ + ($(($struct_name:ident, $link:ident)),+ $(,)?) => { + $( + impl $struct_name { + /// Iterates through the link map of a BPF program. + /// + /// This iterator is immutable (read only). + pub fn links(&self) -> impl Iterator { + self.data.links.iter() + } + } + )+ + } +} + +impl_program_links!( + (KProbe, KProbeLink), + (UProbe, UProbeLink), + (TracePoint, TracePointLink), + (SocketFilter, SocketFilterLink), + (Xdp, XdpLink), + (SkMsg, SkMsgLink), + (SkSkb, SkSkbLink), + (SchedClassifier, SchedClassifierLink), + (CgroupSkb, CgroupSkbLink), + (CgroupSysctl, CgroupSysctlLink), + (CgroupSockopt, CgroupSockoptLink), + (LircMode2, LircLink), + (PerfEvent, PerfLink), + (Lsm, LsmLink), + (RawTracePoint, RawTracePointLink), + (BtfTracePoint, BtfTracePointLink), + (FEntry, FEntryLink), + (FExit, FExitLink), + (Extension, ExtensionLink), + (CgroupSockAddr, CgroupSockAddrLink), + (SkLookup, SkLookupLink), + (SockOps, SockOpsLink), + (CgroupSock, CgroupSockLink), + (CgroupDevice, CgroupDeviceLink), +); + macro_rules! impl_program_pin{ ($($struct_name:ident),+ $(,)?) => { $( diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs index 9b14858d..a343c825 100644 --- a/aya/src/programs/tc.rs +++ b/aya/src/programs/tc.rs @@ -10,6 +10,7 @@ use crate::{ generated::{ bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS, TC_H_CLSACT, TC_H_MIN_EGRESS, TC_H_MIN_INGRESS, }, + interface::NetworkInterface, programs::{define_link_wrapper, load_program, Link, ProgramData, ProgramError}, sys::{ netlink_find_filter_with_name, netlink_qdisc_add_clsact, netlink_qdisc_attach, @@ -192,6 +193,15 @@ impl SchedClassifier { } } +impl SchedClassifierLink { + /// Provides the linked [NetworkInterface]. + pub fn interface(self) -> NetworkInterface { + NetworkInterface { + index: self.0.if_index + } + } +} + #[derive(Debug, Hash, Eq, PartialEq)] pub(crate) struct TcLinkId(i32, TcAttachType, u16, u32); diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs index a979bc08..b6e8365f 100644 --- a/aya/src/programs/xdp.rs +++ b/aya/src/programs/xdp.rs @@ -12,6 +12,7 @@ use crate::{ XDP_FLAGS_DRV_MODE, XDP_FLAGS_HW_MODE, XDP_FLAGS_REPLACE, XDP_FLAGS_SKB_MODE, XDP_FLAGS_UPDATE_IF_NOEXIST, }, + interface::NetworkInterface, programs::{ define_link_wrapper, load_program, FdLink, Link, LinkError, ProgramData, ProgramError, }, @@ -193,6 +194,19 @@ pub(crate) struct NlLink { flags: XdpFlags, } +impl XdpLink { + /// Provides a [NetworkInterface] when an XDP link is backed by a network interface. + /// Returns [None] when the link is backed by a file descriptor. + pub fn interface(self) -> Option { + let index = match self.0 { + XdpLinkInner::NlLink(nl_link) => nl_link.if_index, + _ => return None, + }; + + Some(NetworkInterface { index }) + } +} + impl Link for NlLink { type Id = (i32, RawFd);