Move interface.rs into util.rs

Also store if_index on XdpLink
Removes XdpLink::TryFrom<FdLink>
pull/478/head
Ayrton Sparling 3 years ago
parent dc64377836
commit 5e3d51d8f0

@ -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<String, std::io::Error> {
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<String> = NetworkInterface::list()
/// .iter()
/// .map(|interface| interface.name().unwrap())
/// .collect();
/// ```
pub fn list() -> Vec<NetworkInterface> {
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<if_nameindex> for NetworkInterface {
type Error = ();
// Returns Err is the interface is invalid (zeroed)
fn try_from(value: if_nameindex) -> Result<Self, ()> {
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<String> = 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::<i32>().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");
}
}
}

@ -47,7 +47,6 @@ extern crate bitflags;
mod bpf; mod bpf;
mod generated; mod generated;
pub mod interface;
pub mod maps; pub mod maps;
mod obj; mod obj;
pub mod pin; pub mod pin;

@ -3,7 +3,7 @@ use libc::{close, dup};
use thiserror::Error; use thiserror::Error;
use std::{ use std::{
collections::{hash_map::{Entry, self}, HashMap}, collections::{hash_map::Entry, HashMap},
ffi::CString, ffi::CString,
io, io,
os::unix::prelude::RawFd, os::unix::prelude::RawFd,
@ -70,7 +70,7 @@ impl<T: Link> LinkMap<T> {
self.links.remove(&link_id).ok_or(ProgramError::NotAttached) 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<Item = &T> + ExactSizeIterator {
self.links.values() self.links.values()
} }
} }

@ -699,7 +699,7 @@ macro_rules! impl_program_links{
/// Iterates through the link map of a BPF program. /// Iterates through the link map of a BPF program.
/// ///
/// This iterator is immutable (read only). /// This iterator is immutable (read only).
pub fn links(&self) -> impl Iterator<Item=&$link> { pub fn links(&self) -> impl Iterator<Item=&$link> + ExactSizeIterator {
self.data.links.iter() self.data.links.iter()
} }
} }

@ -10,13 +10,12 @@ use crate::{
generated::{ generated::{
bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS, TC_H_CLSACT, TC_H_MIN_EGRESS, TC_H_MIN_INGRESS, 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}, programs::{define_link_wrapper, load_program, Link, ProgramData, ProgramError},
sys::{ sys::{
netlink_find_filter_with_name, netlink_qdisc_add_clsact, netlink_qdisc_attach, netlink_find_filter_with_name, netlink_qdisc_add_clsact, netlink_qdisc_attach,
netlink_qdisc_detach, netlink_qdisc_detach,
}, },
util::{ifindex_from_ifname, tc_handler_make}, util::{ifindex_from_ifname, tc_handler_make, NetworkInterface},
}; };
/// Traffic control attach type. /// Traffic control attach type.
@ -195,9 +194,9 @@ impl SchedClassifier {
impl SchedClassifierLink { impl SchedClassifierLink {
/// Provides the linked [NetworkInterface]. /// Provides the linked [NetworkInterface].
pub fn interface(self) -> NetworkInterface { pub fn interface(&self) -> NetworkInterface {
NetworkInterface { NetworkInterface {
index: self.0.if_index index: self.0.if_index,
} }
} }
} }

@ -7,19 +7,15 @@ use thiserror::Error;
use crate::{ use crate::{
generated::{ generated::{
bpf_attach_type::{self, BPF_XDP}, bpf_attach_type::{self, BPF_XDP},
bpf_link_type,
bpf_prog_type::BPF_PROG_TYPE_XDP, bpf_prog_type::BPF_PROG_TYPE_XDP,
XDP_FLAGS_DRV_MODE, XDP_FLAGS_HW_MODE, XDP_FLAGS_REPLACE, XDP_FLAGS_SKB_MODE, XDP_FLAGS_DRV_MODE, XDP_FLAGS_HW_MODE, XDP_FLAGS_REPLACE, XDP_FLAGS_SKB_MODE,
XDP_FLAGS_UPDATE_IF_NOEXIST, XDP_FLAGS_UPDATE_IF_NOEXIST,
}, },
interface::NetworkInterface,
programs::{ programs::{
define_link_wrapper, load_program, FdLink, Link, LinkError, ProgramData, ProgramError, define_link_wrapper, load_program, FdLink, Link, LinkError, ProgramData, ProgramError,
}, },
sys::{ sys::{bpf_link_create, bpf_link_update, kernel_version, netlink_set_xdp_fd},
bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, kernel_version, util::NetworkInterface,
netlink_set_xdp_fd,
},
}; };
/// The type returned when attaching an [`Xdp`] program fails on kernels `< 5.9`. /// The type returned when attaching an [`Xdp`] program fails on kernels `< 5.9`.
@ -116,18 +112,25 @@ impl Xdp {
io_error, io_error,
}, },
)? as RawFd; )? as RawFd;
self.data
.links let inner = XdpLinkInner {
.insert(XdpLink(XdpLinkInner::FdLink(FdLink::new(link_fd)))) if_index,
link: XdpLinkInnerType::FdLink(FdLink::new(link_fd)),
};
self.data.links.insert(XdpLink(inner))
} else { } else {
unsafe { netlink_set_xdp_fd(if_index, prog_fd, None, flags.bits) } unsafe { netlink_set_xdp_fd(if_index, prog_fd, None, flags.bits) }
.map_err(|io_error| XdpError::NetlinkError { io_error })?; .map_err(|io_error| XdpError::NetlinkError { io_error })?;
self.data.links.insert(XdpLink(XdpLinkInner::NlLink(NlLink { let inner = XdpLinkInner {
if_index,
link: XdpLinkInnerType::NlLink(NlLink {
if_index, if_index,
prog_fd, prog_fd,
flags, flags,
}))) }),
};
self.data.links.insert(XdpLink(inner))
} }
} }
@ -151,8 +154,8 @@ impl Xdp {
/// Ownership of the link will transfer to this program. /// Ownership of the link will transfer to this program.
pub fn attach_to_link(&mut self, link: XdpLink) -> Result<XdpLinkId, ProgramError> { pub fn attach_to_link(&mut self, link: XdpLink) -> Result<XdpLinkId, ProgramError> {
let prog_fd = self.data.fd_or_err()?; let prog_fd = self.data.fd_or_err()?;
match &link.0 { match &link.0.link {
XdpLinkInner::FdLink(fd_link) => { XdpLinkInnerType::FdLink(fd_link) => {
let link_fd = fd_link.fd.unwrap(); let link_fd = fd_link.fd.unwrap();
bpf_link_update(link_fd, prog_fd, None, 0).map_err(|(_, io_error)| { bpf_link_update(link_fd, prog_fd, None, 0).map_err(|(_, io_error)| {
ProgramError::SyscallError { ProgramError::SyscallError {
@ -160,13 +163,16 @@ impl Xdp {
io_error, io_error,
} }
})?; })?;
let if_index = link.0.if_index;
// dispose of link and avoid detach on drop // dispose of link and avoid detach on drop
mem::forget(link); mem::forget(link);
self.data let inner = XdpLinkInner {
.links if_index,
.insert(XdpLink(XdpLinkInner::FdLink(FdLink::new(link_fd)))) 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 if_index = nl_link.if_index;
let old_prog_fd = nl_link.prog_fd; let old_prog_fd = nl_link.prog_fd;
let flags = nl_link.flags; let flags = nl_link.flags;
@ -177,11 +183,15 @@ impl Xdp {
} }
// dispose of link and avoid detach on drop // dispose of link and avoid detach on drop
mem::forget(link); mem::forget(link);
self.data.links.insert(XdpLink(XdpLinkInner::NlLink(NlLink { let inner = XdpLinkInner {
if_index,
link: XdpLinkInnerType::NlLink(NlLink {
if_index, if_index,
prog_fd, prog_fd,
flags, flags,
}))) }),
};
self.data.links.insert(XdpLink(inner))
} }
} }
} }
@ -197,13 +207,10 @@ pub(crate) struct NlLink {
impl XdpLink { impl XdpLink {
/// Provides a [NetworkInterface] when an XDP link is backed by a network interface. /// Provides a [NetworkInterface] when an XDP link is backed by a network interface.
/// Returns [None] when the link is backed by a file descriptor. /// Returns [None] when the link is backed by a file descriptor.
pub fn interface(self) -> Option<NetworkInterface> { pub fn interface(&self) -> NetworkInterface {
let index = match self.0 { NetworkInterface {
XdpLinkInner::NlLink(nl_link) => nl_link.if_index, index: self.0.if_index,
_ => return None, }
};
Some(NetworkInterface { index })
} }
} }
@ -233,25 +240,31 @@ pub(crate) enum XdpLinkIdInner {
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum XdpLinkInner { pub(crate) enum XdpLinkInnerType {
FdLink(FdLink), FdLink(FdLink),
NlLink(NlLink), NlLink(NlLink),
} }
#[derive(Debug)]
pub(crate) struct XdpLinkInner {
if_index: i32,
link: XdpLinkInnerType,
}
impl Link for XdpLinkInner { impl Link for XdpLinkInner {
type Id = XdpLinkIdInner; type Id = XdpLinkIdInner;
fn id(&self) -> Self::Id { fn id(&self) -> Self::Id {
match self { match &self.link {
XdpLinkInner::FdLink(link) => XdpLinkIdInner::FdLinkId(link.id()), XdpLinkInnerType::FdLink(link) => XdpLinkIdInner::FdLinkId(link.id()),
XdpLinkInner::NlLink(link) => XdpLinkIdInner::NlLinkId(link.id()), XdpLinkInnerType::NlLink(link) => XdpLinkIdInner::NlLinkId(link.id()),
} }
} }
fn detach(self) -> Result<(), ProgramError> { fn detach(self) -> Result<(), ProgramError> {
match self { match self.link {
XdpLinkInner::FdLink(link) => link.detach(), XdpLinkInnerType::FdLink(link) => link.detach(),
XdpLinkInner::NlLink(link) => link.detach(), XdpLinkInnerType::NlLink(link) => link.detach(),
} }
} }
} }
@ -260,7 +273,7 @@ impl TryFrom<XdpLink> for FdLink {
type Error = LinkError; type Error = LinkError;
fn try_from(value: XdpLink) -> Result<Self, Self::Error> { fn try_from(value: XdpLink) -> Result<Self, Self::Error> {
if let XdpLinkInner::FdLink(fd) = value.0 { if let XdpLinkInnerType::FdLink(fd) = value.0.link {
Ok(fd) Ok(fd)
} else { } else {
Err(LinkError::InvalidLink) Err(LinkError::InvalidLink)
@ -268,24 +281,28 @@ impl TryFrom<XdpLink> for FdLink {
} }
} }
impl TryFrom<FdLink> for XdpLink { // impl TryFrom<FdLink> for XdpLink {
type Error = LinkError; // type Error = LinkError;
fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> { // fn try_from(fd_link: FdLink) -> Result<Self, Self::Error> {
// unwrap of fd_link.fd will not panic since it's only None when being dropped. // // 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| { // let info = bpf_link_get_info_by_fd(fd_link.fd.unwrap()).map_err(|io_error| {
LinkError::SyscallError { // LinkError::SyscallError {
call: "BPF_OBJ_GET_INFO_BY_FD".to_string(), // call: "BPF_OBJ_GET_INFO_BY_FD".to_string(),
code: 0, // code: 0,
io_error, // io_error,
} // }
})?; // })?;
if info.type_ == (bpf_link_type::BPF_LINK_TYPE_XDP as u32) { // if info.type_ == (bpf_link_type::BPF_LINK_TYPE_XDP as u32) {
return Ok(XdpLink(XdpLinkInner::FdLink(fd_link))); // let inner = XdpLinkInner {
} // if_index: todo!(),
Err(LinkError::InvalidLink) // link: XdpLinkInnerType::FdLink(fd_link),
} // };
} // return Ok(XdpLink(inner));
// }
// Err(LinkError::InvalidLink)
// }
// }
define_link_wrapper!( define_link_wrapper!(
/// The link used by [Xdp] programs. /// The link used by [Xdp] programs.

@ -466,6 +466,7 @@ pub(crate) fn bpf_map_get_info_by_fd(prog_fd: RawFd) -> Result<bpf_map_info, io:
} }
} }
#[allow(dead_code)]
pub(crate) fn bpf_link_get_info_by_fd(link_fd: RawFd) -> Result<bpf_link_info, io::Error> { pub(crate) fn bpf_link_get_info_by_fd(link_fd: RawFd) -> Result<bpf_link_info, io::Error> {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() }; let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
// info gets entirely populated by the kernel // info gets entirely populated by the kernel

@ -10,7 +10,7 @@ use std::{
use crate::generated::{TC_H_MAJ_MASK, TC_H_MIN_MASK}; 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; use io::BufRead;
@ -103,6 +103,22 @@ pub(crate) fn ifindex_from_ifname(if_name: &str) -> Result<u32, io::Error> {
Ok(if_index) Ok(if_index)
} }
pub(crate) fn ifname_from_ifindex(if_index: u32) -> Result<String, std::io::Error> {
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 { pub(crate) fn tc_handler_make(major: u32, minor: u32) -> u32 {
(major & TC_H_MAJ_MASK) | (minor & TC_H_MIN_MASK) (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<String, std::io::Error> {
ifname_from_ifindex(self.index as u32)
}
/// Provides a [Vec] of all operating system network interfaces, including virtual ones.
/// # Example
///
/// ```
/// let interfaces_names: Vec<String> = NetworkInterface::list()
/// .iter()
/// .map(|interface| interface.name().unwrap())
/// .collect();
/// ```
pub fn list() -> Vec<NetworkInterface> {
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<Self, ()> {
if value.if_index == 0 || value.if_name.is_null() {
return Err(());
}
Ok(NetworkInterface {
index: value.if_index as i32,
})
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::ffi::CString;
#[test] #[test]
fn test_parse_online_cpus() { fn test_parse_online_cpus() {
@ -245,4 +320,86 @@ mod tests {
); );
assert_eq!(syms.get(&0x6000u64).unwrap().as_str(), "cpu_tss_rw"); 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<String> = 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::<i32>().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");
}
}
} }

Loading…
Cancel
Save