diff --git a/aya/src/interface.rs b/aya/src/interface.rs deleted file mode 100644 index 8e204a3a..00000000 --- a/aya/src/interface.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! 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 3bcf1546..14f3eb2d 100644 --- a/aya/src/lib.rs +++ b/aya/src/lib.rs @@ -47,7 +47,6 @@ 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 42e12524..af276245 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, self}, HashMap}, + collections::{hash_map::Entry, HashMap}, ffi::CString, io, os::unix::prelude::RawFd, @@ -70,7 +70,7 @@ impl LinkMap { self.links.remove(&link_id).ok_or(ProgramError::NotAttached) } - pub(crate) fn iter(&self) -> hash_map::Values<'_, T::Id, T> { + pub(crate) fn iter(&self) -> impl Iterator + ExactSizeIterator { self.links.values() } } @@ -374,7 +374,7 @@ mod tests { 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(); @@ -382,7 +382,7 @@ mod tests { 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]; diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 996a9c26..3904cda0 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -699,7 +699,7 @@ macro_rules! impl_program_links{ /// Iterates through the link map of a BPF program. /// /// This iterator is immutable (read only). - pub fn links(&self) -> impl Iterator { + pub fn links(&self) -> impl Iterator + ExactSizeIterator { self.data.links.iter() } } diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs index a343c825..0eb9a419 100644 --- a/aya/src/programs/tc.rs +++ b/aya/src/programs/tc.rs @@ -10,13 +10,12 @@ 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, netlink_qdisc_detach, }, - util::{ifindex_from_ifname, tc_handler_make}, + util::{ifindex_from_ifname, tc_handler_make, NetworkInterface}, }; /// Traffic control attach type. @@ -195,9 +194,9 @@ impl SchedClassifier { impl SchedClassifierLink { /// Provides the linked [NetworkInterface]. - pub fn interface(self) -> NetworkInterface { + pub fn interface(&self) -> NetworkInterface { NetworkInterface { - index: self.0.if_index + index: self.0.if_index, } } } diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs index b6e8365f..38188eff 100644 --- a/aya/src/programs/xdp.rs +++ b/aya/src/programs/xdp.rs @@ -7,19 +7,15 @@ use thiserror::Error; use crate::{ generated::{ bpf_attach_type::{self, BPF_XDP}, - bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_XDP, 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, }, - sys::{ - bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, kernel_version, - netlink_set_xdp_fd, - }, + sys::{bpf_link_create, bpf_link_update, kernel_version, netlink_set_xdp_fd}, + util::NetworkInterface, }; /// The type returned when attaching an [`Xdp`] program fails on kernels `< 5.9`. @@ -116,18 +112,25 @@ impl Xdp { io_error, }, )? as RawFd; - self.data - .links - .insert(XdpLink(XdpLinkInner::FdLink(FdLink::new(link_fd)))) + + let inner = XdpLinkInner { + if_index, + link: XdpLinkInnerType::FdLink(FdLink::new(link_fd)), + }; + self.data.links.insert(XdpLink(inner)) } else { unsafe { netlink_set_xdp_fd(if_index, prog_fd, None, flags.bits) } .map_err(|io_error| XdpError::NetlinkError { io_error })?; - self.data.links.insert(XdpLink(XdpLinkInner::NlLink(NlLink { + let inner = XdpLinkInner { if_index, - prog_fd, - flags, - }))) + link: XdpLinkInnerType::NlLink(NlLink { + if_index, + prog_fd, + flags, + }), + }; + self.data.links.insert(XdpLink(inner)) } } @@ -151,8 +154,8 @@ impl Xdp { /// Ownership of the link will transfer to this program. pub fn attach_to_link(&mut self, link: XdpLink) -> Result { let prog_fd = self.data.fd_or_err()?; - match &link.0 { - XdpLinkInner::FdLink(fd_link) => { + match &link.0.link { + XdpLinkInnerType::FdLink(fd_link) => { let link_fd = fd_link.fd.unwrap(); bpf_link_update(link_fd, prog_fd, None, 0).map_err(|(_, io_error)| { ProgramError::SyscallError { @@ -160,13 +163,16 @@ impl Xdp { io_error, } })?; + let if_index = link.0.if_index; // dispose of link and avoid detach on drop mem::forget(link); - self.data - .links - .insert(XdpLink(XdpLinkInner::FdLink(FdLink::new(link_fd)))) + let inner = XdpLinkInner { + if_index, + link: XdpLinkInnerType::FdLink(FdLink::new(link_fd)), + }; + self.data.links.insert(XdpLink(inner)) } - XdpLinkInner::NlLink(nl_link) => { + XdpLinkInnerType::NlLink(nl_link) => { let if_index = nl_link.if_index; let old_prog_fd = nl_link.prog_fd; let flags = nl_link.flags; @@ -177,11 +183,15 @@ impl Xdp { } // dispose of link and avoid detach on drop mem::forget(link); - self.data.links.insert(XdpLink(XdpLinkInner::NlLink(NlLink { + let inner = XdpLinkInner { if_index, - prog_fd, - flags, - }))) + link: XdpLinkInnerType::NlLink(NlLink { + if_index, + prog_fd, + flags, + }), + }; + self.data.links.insert(XdpLink(inner)) } } } @@ -197,13 +207,10 @@ pub(crate) struct NlLink { 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 }) + pub fn interface(&self) -> NetworkInterface { + NetworkInterface { + index: self.0.if_index, + } } } @@ -233,25 +240,31 @@ pub(crate) enum XdpLinkIdInner { } #[derive(Debug)] -pub(crate) enum XdpLinkInner { +pub(crate) enum XdpLinkInnerType { FdLink(FdLink), NlLink(NlLink), } +#[derive(Debug)] +pub(crate) struct XdpLinkInner { + if_index: i32, + link: XdpLinkInnerType, +} + impl Link for XdpLinkInner { type Id = XdpLinkIdInner; fn id(&self) -> Self::Id { - match self { - XdpLinkInner::FdLink(link) => XdpLinkIdInner::FdLinkId(link.id()), - XdpLinkInner::NlLink(link) => XdpLinkIdInner::NlLinkId(link.id()), + match &self.link { + XdpLinkInnerType::FdLink(link) => XdpLinkIdInner::FdLinkId(link.id()), + XdpLinkInnerType::NlLink(link) => XdpLinkIdInner::NlLinkId(link.id()), } } fn detach(self) -> Result<(), ProgramError> { - match self { - XdpLinkInner::FdLink(link) => link.detach(), - XdpLinkInner::NlLink(link) => link.detach(), + match self.link { + XdpLinkInnerType::FdLink(link) => link.detach(), + XdpLinkInnerType::NlLink(link) => link.detach(), } } } @@ -260,7 +273,7 @@ impl TryFrom for FdLink { type Error = LinkError; fn try_from(value: XdpLink) -> Result { - if let XdpLinkInner::FdLink(fd) = value.0 { + if let XdpLinkInnerType::FdLink(fd) = value.0.link { Ok(fd) } else { Err(LinkError::InvalidLink) @@ -268,24 +281,28 @@ impl TryFrom for FdLink { } } -impl TryFrom for XdpLink { - type Error = LinkError; +// impl TryFrom for XdpLink { +// type Error = LinkError; - fn try_from(fd_link: FdLink) -> Result { - // unwrap of fd_link.fd will not panic since it's only None when being dropped. - let info = bpf_link_get_info_by_fd(fd_link.fd.unwrap()).map_err(|io_error| { - LinkError::SyscallError { - call: "BPF_OBJ_GET_INFO_BY_FD".to_string(), - code: 0, - io_error, - } - })?; - if info.type_ == (bpf_link_type::BPF_LINK_TYPE_XDP as u32) { - return Ok(XdpLink(XdpLinkInner::FdLink(fd_link))); - } - Err(LinkError::InvalidLink) - } -} +// fn try_from(fd_link: FdLink) -> Result { +// // unwrap of fd_link.fd will not panic since it's only None when being dropped. +// let info = bpf_link_get_info_by_fd(fd_link.fd.unwrap()).map_err(|io_error| { +// LinkError::SyscallError { +// call: "BPF_OBJ_GET_INFO_BY_FD".to_string(), +// code: 0, +// io_error, +// } +// })?; +// if info.type_ == (bpf_link_type::BPF_LINK_TYPE_XDP as u32) { +// let inner = XdpLinkInner { +// if_index: todo!(), +// link: XdpLinkInnerType::FdLink(fd_link), +// }; +// return Ok(XdpLink(inner)); +// } +// Err(LinkError::InvalidLink) +// } +// } define_link_wrapper!( /// The link used by [Xdp] programs. diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 43d08fbd..941bd0ed 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -466,6 +466,7 @@ pub(crate) fn bpf_map_get_info_by_fd(prog_fd: RawFd) -> Result Result { let mut attr = unsafe { mem::zeroed::() }; // info gets entirely populated by the kernel diff --git a/aya/src/util.rs b/aya/src/util.rs index 34a82681..cf5716aa 100644 --- a/aya/src/util.rs +++ b/aya/src/util.rs @@ -10,7 +10,7 @@ use std::{ use crate::generated::{TC_H_MAJ_MASK, TC_H_MIN_MASK}; -use libc::{if_nametoindex, sysconf, _SC_PAGESIZE}; +use libc::{if_nameindex, if_nametoindex, sysconf, _SC_PAGESIZE}; use io::BufRead; @@ -103,6 +103,22 @@ pub(crate) fn ifindex_from_ifname(if_name: &str) -> Result { Ok(if_index) } +pub(crate) fn ifname_from_ifindex(if_index: u32) -> 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(if_index, 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()) +} + pub(crate) fn tc_handler_make(major: u32, minor: u32) -> u32 { (major & TC_H_MAJ_MASK) | (minor & TC_H_MIN_MASK) } @@ -206,9 +222,68 @@ impl VerifierLog { } } +#[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 { + ifname_from_ifindex(self.index as u32) + } + + /// 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::from_ifnameindex(unsafe { *curr }) { + list.push(interface); + curr = unsafe { curr.add(1) }; + } + + unsafe { + libc::if_freenameindex(head); + }; + + list + } + + // Returns Err is the interface is invalid (zeroed) + fn from_ifnameindex(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 super::*; + use std::ffi::CString; #[test] fn test_parse_online_cpus() { @@ -245,4 +320,86 @@ mod tests { ); assert_eq!(syms.get(&0x6000u64).unwrap().as_str(), "cpu_tss_rw"); } + + #[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_from_ifnameindex() { + 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::from_ifnameindex(k_interface).unwrap(); + + assert_eq!(interface.index(), 1); + + let invalid_k_interface = if_nameindex { + if_index: 0, + if_name: null_mut(), + }; + + let res = NetworkInterface::from_ifnameindex(invalid_k_interface); + assert_eq!(res.unwrap_err(), ()); + + let invalid_k_interface = if_nameindex { + if_index: 1, + if_name: null_mut(), + }; + + let res = NetworkInterface::from_ifnameindex(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"); + } + } }