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 <ayrton@sparling.us>
pull/478/head
Ayrton Sparling 3 years ago
parent 9f5d157628
commit dc64377836
No known key found for this signature in database
GPG Key ID: 36C6BC444BECABA3

@ -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<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,6 +47,7 @@ extern crate bitflags;
mod bpf;
mod generated;
pub mod interface;
pub mod maps;
mod obj;
pub mod pin;

@ -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<T: Link> LinkMap<T> {
pub(crate) fn forget(&mut self, link_id: T::Id) -> Result<T, ProgramError> {
self.links.remove(&link_id).ok_or(ProgramError::NotAttached)
}
pub(crate) fn iter(&self) -> hash_map::Values<'_, T::Id, T> {
self.links.values()
}
}
impl<T: Link> Drop for LinkMap<T> {
@ -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<RefCell<u8>>,
@ -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();

@ -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<Item=&$link> {
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),+ $(,)?) => {
$(

@ -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);

@ -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<NetworkInterface> {
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);

Loading…
Cancel
Save