From dc64377836276cc2734562403b7d7ff9a9727bfb Mon Sep 17 00:00:00 2001 From: Ayrton Sparling Date: Wed, 28 Dec 2022 19:33:43 -0800 Subject: [PATCH] Add program::links() and NetworkInterface NetworkInterface provides a way to store network interface indexes as well as retrieve an interface's name using the NetworkInterface.name() method. Interfaces can be created manually or retrieved via new .interface() methods on XdpLink and SchedClassifierLink. Interface names are extracted from the kernel for 2 reasons. 1. Interface names can change within the kernel at anytime which aya will not be aware of. 2. Names are not always available when a NetworkInterface is created. For example in the XdpLink .interface() implementation. A new method NetworkInterface::list() also provides a list of network interfaces from the kernel. Signed-off-by: Ayrton Sparling --- aya/src/interface.rs | 168 ++++++++++++++++++++++++++++++++++++++ aya/src/lib.rs | 1 + aya/src/programs/links.rs | 28 ++++++- aya/src/programs/mod.rs | 92 +++++++++++++++------ aya/src/programs/tc.rs | 10 +++ aya/src/programs/xdp.rs | 14 ++++ 6 files changed, 288 insertions(+), 25 deletions(-) create mode 100644 aya/src/interface.rs 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);