mirror of https://github.com/aya-rs/aya
Merge branch 'main' into new_sarg
commit
5ed278a766
@ -0,0 +1,24 @@
|
|||||||
|
//! XDP programs.
|
||||||
|
|
||||||
|
use crate::generated::bpf_attach_type;
|
||||||
|
|
||||||
|
/// Defines where to attach an `XDP` program.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum XdpAttachType {
|
||||||
|
/// Attach to a network interface.
|
||||||
|
Interface,
|
||||||
|
/// Attach to a cpumap. Requires kernel 5.9 or later.
|
||||||
|
CpuMap,
|
||||||
|
/// Attach to a devmap. Requires kernel 5.8 or later.
|
||||||
|
DevMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<XdpAttachType> for bpf_attach_type {
|
||||||
|
fn from(value: XdpAttachType) -> Self {
|
||||||
|
match value {
|
||||||
|
XdpAttachType::Interface => bpf_attach_type::BPF_XDP,
|
||||||
|
XdpAttachType::CpuMap => bpf_attach_type::BPF_XDP_CPUMAP,
|
||||||
|
XdpAttachType::DevMap => bpf_attach_type::BPF_XDP_DEVMAP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,195 @@
|
|||||||
|
//! An array of available CPUs.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::{Borrow, BorrowMut},
|
||||||
|
num::NonZeroU32,
|
||||||
|
os::fd::{AsFd, AsRawFd},
|
||||||
|
};
|
||||||
|
|
||||||
|
use aya_obj::generated::bpf_cpumap_val;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError},
|
||||||
|
programs::ProgramFd,
|
||||||
|
sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError},
|
||||||
|
Pod, FEATURES,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::XdpMapError;
|
||||||
|
|
||||||
|
/// An array of available CPUs.
|
||||||
|
///
|
||||||
|
/// XDP programs can use this map to redirect packets to a target
|
||||||
|
/// CPU for processing.
|
||||||
|
///
|
||||||
|
/// # Minimum kernel version
|
||||||
|
///
|
||||||
|
/// The minimum kernel version required to use this feature is 4.15.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```no_run
|
||||||
|
/// # let elf_bytes = &[];
|
||||||
|
/// use aya::maps::xdp::CpuMap;
|
||||||
|
///
|
||||||
|
/// let ncpus = aya::util::nr_cpus().unwrap() as u32;
|
||||||
|
/// let mut bpf = aya::BpfLoader::new()
|
||||||
|
/// .set_max_entries("CPUS", ncpus)
|
||||||
|
/// .load(elf_bytes)
|
||||||
|
/// .unwrap();
|
||||||
|
/// let mut cpumap = CpuMap::try_from(bpf.map_mut("CPUS").unwrap())?;
|
||||||
|
/// let flags = 0;
|
||||||
|
/// let queue_size = 2048;
|
||||||
|
/// for i in 0..ncpus {
|
||||||
|
/// cpumap.set(i, queue_size, None, flags);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # Ok::<(), aya::BpfError>(())
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # See also
|
||||||
|
///
|
||||||
|
/// Kernel documentation: <https://docs.kernel.org/next/bpf/map_cpumap.html>
|
||||||
|
#[doc(alias = "BPF_MAP_TYPE_CPUMAP")]
|
||||||
|
pub struct CpuMap<T> {
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Borrow<MapData>> CpuMap<T> {
|
||||||
|
pub(crate) fn new(map: T) -> Result<Self, MapError> {
|
||||||
|
let data = map.borrow();
|
||||||
|
|
||||||
|
if FEATURES.cpumap_prog_id() {
|
||||||
|
check_kv_size::<u32, bpf_cpumap_val>(data)?;
|
||||||
|
} else {
|
||||||
|
check_kv_size::<u32, u32>(data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { inner: map })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of elements in the array.
|
||||||
|
///
|
||||||
|
/// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side.
|
||||||
|
pub fn len(&self) -> u32 {
|
||||||
|
self.inner.borrow().obj.max_entries()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the queue size and optional program for a given CPU index.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`MapError::OutOfBounds`] if `cpu_index` is out of bounds,
|
||||||
|
/// [`MapError::SyscallError`] if `bpf_map_lookup_elem` fails.
|
||||||
|
pub fn get(&self, cpu_index: u32, flags: u64) -> Result<CpuMapValue, MapError> {
|
||||||
|
let data = self.inner.borrow();
|
||||||
|
check_bounds(data, cpu_index)?;
|
||||||
|
let fd = data.fd().as_fd();
|
||||||
|
|
||||||
|
let value = if FEATURES.cpumap_prog_id() {
|
||||||
|
bpf_map_lookup_elem::<_, bpf_cpumap_val>(fd, &cpu_index, flags).map(|value| {
|
||||||
|
value.map(|value| CpuMapValue {
|
||||||
|
queue_size: value.qsize,
|
||||||
|
// SAFETY: map writes use fd, map reads use id.
|
||||||
|
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6241
|
||||||
|
prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
bpf_map_lookup_elem::<_, u32>(fd, &cpu_index, flags).map(|value| {
|
||||||
|
value.map(|qsize| CpuMapValue {
|
||||||
|
queue_size: qsize,
|
||||||
|
prog_id: None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
value
|
||||||
|
.map_err(|(_, io_error)| SyscallError {
|
||||||
|
call: "bpf_map_lookup_elem",
|
||||||
|
io_error,
|
||||||
|
})?
|
||||||
|
.ok_or(MapError::KeyNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over the elements of the map.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = Result<CpuMapValue, MapError>> + '_ {
|
||||||
|
(0..self.len()).map(move |i| self.get(i, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: BorrowMut<MapData>> CpuMap<T> {
|
||||||
|
/// Sets the queue size at the given CPU index, and optionally a chained program.
|
||||||
|
///
|
||||||
|
/// When sending the packet to the CPU at the given index, the kernel will queue up to
|
||||||
|
/// `queue_size` packets before dropping them.
|
||||||
|
///
|
||||||
|
/// Starting from Linux kernel 5.9, another XDP program can be passed in that will be run on the
|
||||||
|
/// target CPU, instead of the CPU that receives the packets. This allows to perform minimal
|
||||||
|
/// computations on CPUs that directly handle packets from a NIC's RX queues, and perform
|
||||||
|
/// possibly heavier ones in other, less busy CPUs.
|
||||||
|
///
|
||||||
|
/// The chained program must be loaded with the `BPF_XDP_CPUMAP` attach type. When using
|
||||||
|
/// `aya-ebpf`, that means XDP programs that specify the `map = "cpumap"` argument. See the
|
||||||
|
/// kernel-space `aya_ebpf::xdp` for more information.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
|
||||||
|
/// if `bpf_map_update_elem` fails, [`XdpMapError::ChainedProgramNotSupported`] if the kernel
|
||||||
|
/// does not support chained programs and one is provided.
|
||||||
|
pub fn set(
|
||||||
|
&mut self,
|
||||||
|
cpu_index: u32,
|
||||||
|
queue_size: u32,
|
||||||
|
program: Option<&ProgramFd>,
|
||||||
|
flags: u64,
|
||||||
|
) -> Result<(), XdpMapError> {
|
||||||
|
let data = self.inner.borrow_mut();
|
||||||
|
check_bounds(data, cpu_index)?;
|
||||||
|
let fd = data.fd().as_fd();
|
||||||
|
|
||||||
|
let res = if FEATURES.cpumap_prog_id() {
|
||||||
|
let mut value = unsafe { std::mem::zeroed::<bpf_cpumap_val>() };
|
||||||
|
value.qsize = queue_size;
|
||||||
|
// Default is valid as the kernel will only consider fd > 0:
|
||||||
|
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/cpumap.c#L466
|
||||||
|
value.bpf_prog.fd = program
|
||||||
|
.map(|prog| prog.as_fd().as_raw_fd())
|
||||||
|
.unwrap_or_default();
|
||||||
|
bpf_map_update_elem(fd, Some(&cpu_index), &value, flags)
|
||||||
|
} else {
|
||||||
|
if program.is_some() {
|
||||||
|
return Err(XdpMapError::ChainedProgramNotSupported);
|
||||||
|
}
|
||||||
|
bpf_map_update_elem(fd, Some(&cpu_index), &queue_size, flags)
|
||||||
|
};
|
||||||
|
|
||||||
|
res.map_err(|(_, io_error)| {
|
||||||
|
MapError::from(SyscallError {
|
||||||
|
call: "bpf_map_update_elem",
|
||||||
|
io_error,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Borrow<MapData>> IterableMap<u32, CpuMapValue> for CpuMap<T> {
|
||||||
|
fn map(&self) -> &MapData {
|
||||||
|
self.inner.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, key: &u32) -> Result<CpuMapValue, MapError> {
|
||||||
|
self.get(*key, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Pod for bpf_cpumap_val {}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
/// The value of a CPU map.
|
||||||
|
pub struct CpuMapValue {
|
||||||
|
/// Size of the for the CPU.
|
||||||
|
pub queue_size: u32,
|
||||||
|
/// Chained XDP program ID.
|
||||||
|
pub prog_id: Option<NonZeroU32>,
|
||||||
|
}
|
@ -0,0 +1,187 @@
|
|||||||
|
//! An array of network devices.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::{Borrow, BorrowMut},
|
||||||
|
num::NonZeroU32,
|
||||||
|
os::fd::{AsFd, AsRawFd},
|
||||||
|
};
|
||||||
|
|
||||||
|
use aya_obj::generated::bpf_devmap_val;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError},
|
||||||
|
programs::ProgramFd,
|
||||||
|
sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError},
|
||||||
|
Pod, FEATURES,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::XdpMapError;
|
||||||
|
|
||||||
|
/// An array of network devices.
|
||||||
|
///
|
||||||
|
/// XDP programs can use this map to redirect to other network
|
||||||
|
/// devices.
|
||||||
|
///
|
||||||
|
/// # Minimum kernel version
|
||||||
|
///
|
||||||
|
/// The minimum kernel version required to use this feature is 4.14.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```no_run
|
||||||
|
/// # let mut bpf = aya::Bpf::load(&[])?;
|
||||||
|
/// use aya::maps::xdp::DevMap;
|
||||||
|
///
|
||||||
|
/// let mut devmap = DevMap::try_from(bpf.map_mut("IFACES").unwrap())?;
|
||||||
|
/// // Lookups at index 2 will redirect packets to interface with index 3 (e.g. eth1)
|
||||||
|
/// devmap.set(2, 3, None, 0);
|
||||||
|
///
|
||||||
|
/// # Ok::<(), aya::BpfError>(())
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # See also
|
||||||
|
///
|
||||||
|
/// Kernel documentation: <https://docs.kernel.org/next/bpf/map_devmap.html>
|
||||||
|
#[doc(alias = "BPF_MAP_TYPE_DEVMAP")]
|
||||||
|
pub struct DevMap<T> {
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Borrow<MapData>> DevMap<T> {
|
||||||
|
pub(crate) fn new(map: T) -> Result<Self, MapError> {
|
||||||
|
let data = map.borrow();
|
||||||
|
|
||||||
|
if FEATURES.devmap_prog_id() {
|
||||||
|
check_kv_size::<u32, bpf_devmap_val>(data)?;
|
||||||
|
} else {
|
||||||
|
check_kv_size::<u32, u32>(data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { inner: map })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of elements in the array.
|
||||||
|
///
|
||||||
|
/// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side.
|
||||||
|
pub fn len(&self) -> u32 {
|
||||||
|
self.inner.borrow().obj.max_entries()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the target interface index and optional program at a given index.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
|
||||||
|
/// if `bpf_map_lookup_elem` fails.
|
||||||
|
pub fn get(&self, index: u32, flags: u64) -> Result<DevMapValue, MapError> {
|
||||||
|
let data = self.inner.borrow();
|
||||||
|
check_bounds(data, index)?;
|
||||||
|
let fd = data.fd().as_fd();
|
||||||
|
|
||||||
|
let value = if FEATURES.devmap_prog_id() {
|
||||||
|
bpf_map_lookup_elem::<_, bpf_devmap_val>(fd, &index, flags).map(|value| {
|
||||||
|
value.map(|value| DevMapValue {
|
||||||
|
if_index: value.ifindex,
|
||||||
|
// SAFETY: map writes use fd, map reads use id.
|
||||||
|
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228
|
||||||
|
prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
bpf_map_lookup_elem::<_, u32>(fd, &index, flags).map(|value| {
|
||||||
|
value.map(|ifindex| DevMapValue {
|
||||||
|
if_index: ifindex,
|
||||||
|
prog_id: None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
value
|
||||||
|
.map_err(|(_, io_error)| SyscallError {
|
||||||
|
call: "bpf_map_lookup_elem",
|
||||||
|
io_error,
|
||||||
|
})?
|
||||||
|
.ok_or(MapError::KeyNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over the elements of the array.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = Result<DevMapValue, MapError>> + '_ {
|
||||||
|
(0..self.len()).map(move |i| self.get(i, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: BorrowMut<MapData>> DevMap<T> {
|
||||||
|
/// Sets the target interface index at index, and optionally a chained program.
|
||||||
|
///
|
||||||
|
/// When redirecting using `index`, packets will be transmitted by the interface with
|
||||||
|
/// `target_if_index`.
|
||||||
|
///
|
||||||
|
/// Starting from Linux kernel 5.8, another XDP program can be passed in that will be run before
|
||||||
|
/// actual transmission. It can be used to modify the packet before transmission with NIC
|
||||||
|
/// specific data (MAC address update, checksum computations, etc) or other purposes.
|
||||||
|
///
|
||||||
|
/// The chained program must be loaded with the `BPF_XDP_DEVMAP` attach type. When using
|
||||||
|
/// `aya-ebpf`, that means XDP programs that specify the `map = "devmap"` argument. See the
|
||||||
|
/// kernel-space `aya_ebpf::xdp` for more information.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
|
||||||
|
/// if `bpf_map_update_elem` fails, [`MapError::ProgIdNotSupported`] if the kernel does not
|
||||||
|
/// support chained programs and one is provided.
|
||||||
|
pub fn set(
|
||||||
|
&mut self,
|
||||||
|
index: u32,
|
||||||
|
target_if_index: u32,
|
||||||
|
program: Option<&ProgramFd>,
|
||||||
|
flags: u64,
|
||||||
|
) -> Result<(), XdpMapError> {
|
||||||
|
let data = self.inner.borrow_mut();
|
||||||
|
check_bounds(data, index)?;
|
||||||
|
let fd = data.fd().as_fd();
|
||||||
|
|
||||||
|
let res = if FEATURES.devmap_prog_id() {
|
||||||
|
let mut value = unsafe { std::mem::zeroed::<bpf_devmap_val>() };
|
||||||
|
value.ifindex = target_if_index;
|
||||||
|
// Default is valid as the kernel will only consider fd > 0:
|
||||||
|
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866
|
||||||
|
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918
|
||||||
|
value.bpf_prog.fd = program
|
||||||
|
.map(|prog| prog.as_fd().as_raw_fd())
|
||||||
|
.unwrap_or_default();
|
||||||
|
bpf_map_update_elem(fd, Some(&index), &value, flags)
|
||||||
|
} else {
|
||||||
|
if program.is_some() {
|
||||||
|
return Err(XdpMapError::ChainedProgramNotSupported);
|
||||||
|
}
|
||||||
|
bpf_map_update_elem(fd, Some(&index), &target_if_index, flags)
|
||||||
|
};
|
||||||
|
|
||||||
|
res.map_err(|(_, io_error)| {
|
||||||
|
MapError::from(SyscallError {
|
||||||
|
call: "bpf_map_update_elem",
|
||||||
|
io_error,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Borrow<MapData>> IterableMap<u32, DevMapValue> for DevMap<T> {
|
||||||
|
fn map(&self) -> &MapData {
|
||||||
|
self.inner.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, key: &u32) -> Result<DevMapValue, MapError> {
|
||||||
|
self.get(*key, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Pod for bpf_devmap_val {}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
/// The value of a device map.
|
||||||
|
pub struct DevMapValue {
|
||||||
|
/// Target interface index to redirect to.
|
||||||
|
pub if_index: u32,
|
||||||
|
/// Chained XDP program ID.
|
||||||
|
pub prog_id: Option<NonZeroU32>,
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
//! An hashmap of network devices.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::{Borrow, BorrowMut},
|
||||||
|
num::NonZeroU32,
|
||||||
|
os::fd::{AsFd, AsRawFd},
|
||||||
|
};
|
||||||
|
|
||||||
|
use aya_obj::generated::bpf_devmap_val;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
maps::{check_kv_size, hash_map, IterableMap, MapData, MapError, MapIter, MapKeys},
|
||||||
|
programs::ProgramFd,
|
||||||
|
sys::{bpf_map_lookup_elem, SyscallError},
|
||||||
|
FEATURES,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{dev_map::DevMapValue, XdpMapError};
|
||||||
|
|
||||||
|
/// An hashmap of network devices.
|
||||||
|
///
|
||||||
|
/// XDP programs can use this map to redirect to other network
|
||||||
|
/// devices.
|
||||||
|
///
|
||||||
|
/// # Minimum kernel version
|
||||||
|
///
|
||||||
|
/// The minimum kernel version required to use this feature is 5.4.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```no_run
|
||||||
|
/// # let mut bpf = aya::Bpf::load(&[])?;
|
||||||
|
/// use aya::maps::xdp::DevMapHash;
|
||||||
|
///
|
||||||
|
/// let mut devmap = DevMapHash::try_from(bpf.map_mut("IFACES").unwrap())?;
|
||||||
|
/// // Lookups with key 2 will redirect packets to interface with index 3 (e.g. eth1)
|
||||||
|
/// devmap.insert(2, 3, None, 0);
|
||||||
|
///
|
||||||
|
/// # Ok::<(), aya::BpfError>(())
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # See also
|
||||||
|
///
|
||||||
|
/// Kernel documentation: <https://docs.kernel.org/next/bpf/map_devmap.html>
|
||||||
|
#[doc(alias = "BPF_MAP_TYPE_DEVMAP_HASH")]
|
||||||
|
pub struct DevMapHash<T> {
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Borrow<MapData>> DevMapHash<T> {
|
||||||
|
pub(crate) fn new(map: T) -> Result<Self, MapError> {
|
||||||
|
let data = map.borrow();
|
||||||
|
|
||||||
|
if FEATURES.devmap_prog_id() {
|
||||||
|
check_kv_size::<u32, bpf_devmap_val>(data)?;
|
||||||
|
} else {
|
||||||
|
check_kv_size::<u32, u32>(data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { inner: map })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the target interface index and optional program for a given key.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`MapError::SyscallError`] if `bpf_map_lookup_elem` fails.
|
||||||
|
pub fn get(&self, key: u32, flags: u64) -> Result<DevMapValue, MapError> {
|
||||||
|
let fd = self.inner.borrow().fd().as_fd();
|
||||||
|
|
||||||
|
let value = if FEATURES.devmap_prog_id() {
|
||||||
|
bpf_map_lookup_elem::<_, bpf_devmap_val>(fd, &key, flags).map(|value| {
|
||||||
|
value.map(|value| DevMapValue {
|
||||||
|
if_index: value.ifindex,
|
||||||
|
// SAFETY: map writes use fd, map reads use id.
|
||||||
|
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228
|
||||||
|
prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
bpf_map_lookup_elem::<_, u32>(fd, &key, flags).map(|value| {
|
||||||
|
value.map(|ifindex| DevMapValue {
|
||||||
|
if_index: ifindex,
|
||||||
|
prog_id: None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
value
|
||||||
|
.map_err(|(_, io_error)| SyscallError {
|
||||||
|
call: "bpf_map_lookup_elem",
|
||||||
|
io_error,
|
||||||
|
})?
|
||||||
|
.ok_or(MapError::KeyNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over the elements of the devmap in arbitrary order.
|
||||||
|
pub fn iter(&self) -> MapIter<'_, u32, DevMapValue, Self> {
|
||||||
|
MapIter::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator visiting all keys in arbitrary order.
|
||||||
|
pub fn keys(&self) -> MapKeys<'_, u32> {
|
||||||
|
MapKeys::new(self.inner.borrow())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: BorrowMut<MapData>> DevMapHash<T> {
|
||||||
|
/// Inserts an ifindex and optionally a chained program in the map.
|
||||||
|
///
|
||||||
|
/// When redirecting using `key`, packets will be transmitted by the interface with `ifindex`.
|
||||||
|
///
|
||||||
|
/// Starting from Linux kernel 5.8, another XDP program can be passed in that will be run before
|
||||||
|
/// actual transmission. It can be used to modify the packet before transmission with NIC
|
||||||
|
/// specific data (MAC address update, checksum computations, etc) or other purposes.
|
||||||
|
///
|
||||||
|
/// The chained program must be loaded with the `BPF_XDP_DEVMAP` attach type. When using
|
||||||
|
/// `aya-ebpf`, that means XDP programs that specify the `map = "devmap"` argument. See the
|
||||||
|
/// kernel-space `aya_ebpf::xdp` for more information.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`MapError::SyscallError`] if `bpf_map_update_elem` fails,
|
||||||
|
/// [`MapError::ProgIdNotSupported`] if the kernel does not support chained programs and one is
|
||||||
|
/// provided.
|
||||||
|
pub fn insert(
|
||||||
|
&mut self,
|
||||||
|
key: u32,
|
||||||
|
target_if_index: u32,
|
||||||
|
program: Option<&ProgramFd>,
|
||||||
|
flags: u64,
|
||||||
|
) -> Result<(), XdpMapError> {
|
||||||
|
if FEATURES.devmap_prog_id() {
|
||||||
|
let mut value = unsafe { std::mem::zeroed::<bpf_devmap_val>() };
|
||||||
|
value.ifindex = target_if_index;
|
||||||
|
// Default is valid as the kernel will only consider fd > 0:
|
||||||
|
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866
|
||||||
|
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918
|
||||||
|
value.bpf_prog.fd = program
|
||||||
|
.map(|prog| prog.as_fd().as_raw_fd())
|
||||||
|
.unwrap_or_default();
|
||||||
|
hash_map::insert(self.inner.borrow_mut(), &key, &value, flags)?;
|
||||||
|
} else {
|
||||||
|
if program.is_some() {
|
||||||
|
return Err(XdpMapError::ChainedProgramNotSupported);
|
||||||
|
}
|
||||||
|
hash_map::insert(self.inner.borrow_mut(), &key, &target_if_index, flags)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a value from the map.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`MapError::SyscallError`] if `bpf_map_delete_elem` fails.
|
||||||
|
pub fn remove(&mut self, key: u32) -> Result<(), MapError> {
|
||||||
|
hash_map::remove(self.inner.borrow_mut(), &key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Borrow<MapData>> IterableMap<u32, DevMapValue> for DevMapHash<T> {
|
||||||
|
fn map(&self) -> &MapData {
|
||||||
|
self.inner.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, key: &u32) -> Result<DevMapValue, MapError> {
|
||||||
|
self.get(*key, 0)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
//! XDP maps.
|
||||||
|
mod cpu_map;
|
||||||
|
mod dev_map;
|
||||||
|
mod dev_map_hash;
|
||||||
|
mod xsk_map;
|
||||||
|
|
||||||
|
pub use cpu_map::CpuMap;
|
||||||
|
pub use dev_map::DevMap;
|
||||||
|
pub use dev_map_hash::DevMapHash;
|
||||||
|
pub use xsk_map::XskMap;
|
||||||
|
|
||||||
|
use super::MapError;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
/// Errors occuring from working with XDP maps.
|
||||||
|
pub enum XdpMapError {
|
||||||
|
/// Chained programs are not supported.
|
||||||
|
#[error("chained programs are not supported by the current kernel")]
|
||||||
|
ChainedProgramNotSupported,
|
||||||
|
|
||||||
|
/// Map operation failed.
|
||||||
|
#[error(transparent)]
|
||||||
|
MapError(#[from] MapError),
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
//! An array of AF_XDP sockets.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::{Borrow, BorrowMut},
|
||||||
|
os::fd::{AsFd, AsRawFd, RawFd},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
maps::{check_bounds, check_kv_size, MapData, MapError},
|
||||||
|
sys::{bpf_map_update_elem, SyscallError},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An array of AF_XDP sockets.
|
||||||
|
///
|
||||||
|
/// XDP programs can use this map to redirect packets to a target
|
||||||
|
/// AF_XDP socket using the `XDP_REDIRECT` action.
|
||||||
|
///
|
||||||
|
/// # Minimum kernel version
|
||||||
|
///
|
||||||
|
/// The minimum kernel version required to use this feature is 4.18.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```no_run
|
||||||
|
/// # let mut bpf = aya::Bpf::load(&[])?;
|
||||||
|
/// # let socket_fd = 1;
|
||||||
|
/// use aya::maps::XskMap;
|
||||||
|
///
|
||||||
|
/// let mut xskmap = XskMap::try_from(bpf.map_mut("SOCKETS").unwrap())?;
|
||||||
|
/// // socket_fd is the RawFd of an AF_XDP socket
|
||||||
|
/// xskmap.set(0, socket_fd, 0);
|
||||||
|
/// # Ok::<(), aya::BpfError>(())
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # See also
|
||||||
|
///
|
||||||
|
/// Kernel documentation: <https://docs.kernel.org/next/bpf/map_xskmap.html>
|
||||||
|
#[doc(alias = "BPF_MAP_TYPE_XSKMAP")]
|
||||||
|
pub struct XskMap<T> {
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Borrow<MapData>> XskMap<T> {
|
||||||
|
pub(crate) fn new(map: T) -> Result<Self, MapError> {
|
||||||
|
let data = map.borrow();
|
||||||
|
check_kv_size::<u32, RawFd>(data)?;
|
||||||
|
|
||||||
|
Ok(Self { inner: map })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of elements in the array.
|
||||||
|
///
|
||||||
|
/// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side.
|
||||||
|
pub fn len(&self) -> u32 {
|
||||||
|
self.inner.borrow().obj.max_entries()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: BorrowMut<MapData>> XskMap<T> {
|
||||||
|
/// Sets the `AF_XDP` socket at a given index.
|
||||||
|
///
|
||||||
|
/// When redirecting a packet, the `AF_XDP` socket at `index` will recieve the packet. Note
|
||||||
|
/// that it will do so only if the socket is bound to the same queue the packet was recieved
|
||||||
|
/// on.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
|
||||||
|
/// if `bpf_map_update_elem` fails.
|
||||||
|
pub fn set(&mut self, index: u32, socket_fd: impl AsRawFd, flags: u64) -> Result<(), MapError> {
|
||||||
|
let data = self.inner.borrow_mut();
|
||||||
|
check_bounds(data, index)?;
|
||||||
|
let fd = data.fd().as_fd();
|
||||||
|
bpf_map_update_elem(fd, Some(&index), &socket_fd.as_raw_fd(), flags).map_err(
|
||||||
|
|(_, io_error)| SyscallError {
|
||||||
|
call: "bpf_map_update_elem",
|
||||||
|
io_error,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
use core::{cell::UnsafeCell, mem};
|
||||||
|
|
||||||
|
use aya_bpf_bindings::bindings::bpf_cpumap_val;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_CPUMAP},
|
||||||
|
maps::PinningType,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::try_redirect_map;
|
||||||
|
|
||||||
|
/// An array of available CPUs.
|
||||||
|
///
|
||||||
|
/// XDP programs can use this map to redirect packets to a target CPU for processing.
|
||||||
|
///
|
||||||
|
/// # Minimum kernel version
|
||||||
|
///
|
||||||
|
/// The minimum kernel version required to use this feature is 4.15.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::CpuMap, programs::XdpContext};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: CpuMap = CpuMap::with_max_entries(8, 0);
|
||||||
|
///
|
||||||
|
/// #[xdp]
|
||||||
|
/// fn xdp(_ctx: XdpContext) -> i32 {
|
||||||
|
/// // Redirect to CPU 7 or drop packet if no entry found.
|
||||||
|
/// MAP.redirect(7, xdp_action::XDP_DROP as u64)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct CpuMap {
|
||||||
|
def: UnsafeCell<bpf_map_def>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Sync for CpuMap {}
|
||||||
|
|
||||||
|
impl CpuMap {
|
||||||
|
/// Creates a [`CpuMap`] with a set maximum number of elements.
|
||||||
|
///
|
||||||
|
/// In a CPU map, an entry represents a CPU core. Thus there should be as many entries as there
|
||||||
|
/// are CPU cores on the system. `max_entries` can be set to zero here, and updated by userspace
|
||||||
|
/// at runtime. Refer to the userspace documentation for more information.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{macros::map, maps::CpuMap};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: CpuMap = CpuMap::with_max_entries(8, 0);
|
||||||
|
/// ```
|
||||||
|
pub const fn with_max_entries(max_entries: u32, flags: u32) -> CpuMap {
|
||||||
|
CpuMap {
|
||||||
|
def: UnsafeCell::new(bpf_map_def {
|
||||||
|
type_: BPF_MAP_TYPE_CPUMAP,
|
||||||
|
key_size: mem::size_of::<u32>() as u32,
|
||||||
|
value_size: mem::size_of::<bpf_cpumap_val>() as u32,
|
||||||
|
max_entries,
|
||||||
|
map_flags: flags,
|
||||||
|
id: 0,
|
||||||
|
pinning: PinningType::None as u32,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`CpuMap`] with a set maximum number of elements that can be pinned to the BPF
|
||||||
|
/// File System (bpffs).
|
||||||
|
///
|
||||||
|
/// See [`CpuMap::with_max_entries`] for more information.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{macros::map, maps::CpuMap};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: CpuMap = CpuMap::pinned(8, 0);
|
||||||
|
/// ```
|
||||||
|
pub const fn pinned(max_entries: u32, flags: u32) -> CpuMap {
|
||||||
|
CpuMap {
|
||||||
|
def: UnsafeCell::new(bpf_map_def {
|
||||||
|
type_: BPF_MAP_TYPE_CPUMAP,
|
||||||
|
key_size: mem::size_of::<u32>() as u32,
|
||||||
|
value_size: mem::size_of::<bpf_cpumap_val>() as u32,
|
||||||
|
max_entries,
|
||||||
|
map_flags: flags,
|
||||||
|
id: 0,
|
||||||
|
pinning: PinningType::ByName as u32,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redirects the current packet on the CPU at `index`.
|
||||||
|
///
|
||||||
|
/// The lower two bits of `flags` are used for the return code if the map lookup fails, which
|
||||||
|
/// can be used as the XDP program's return code if a CPU cannot be found.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::CpuMap, programs::XdpContext};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: CpuMap = CpuMap::with_max_entries(8, 0);
|
||||||
|
///
|
||||||
|
/// #[xdp]
|
||||||
|
/// fn xdp(_ctx: XdpContext) -> u32 {
|
||||||
|
/// // Redirect to CPU 7 or drop packet if no entry found.
|
||||||
|
/// MAP.redirect(7, 0).unwrap_or(xdp_action::XDP_DROP)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn redirect(&self, index: u32, flags: u64) -> Result<u32, u32> {
|
||||||
|
try_redirect_map(&self.def, index, flags)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
use core::{cell::UnsafeCell, mem, num::NonZeroU32, ptr::NonNull};
|
||||||
|
|
||||||
|
use aya_bpf_bindings::bindings::bpf_devmap_val;
|
||||||
|
use aya_bpf_cty::c_void;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_DEVMAP},
|
||||||
|
helpers::bpf_map_lookup_elem,
|
||||||
|
maps::PinningType,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::try_redirect_map;
|
||||||
|
|
||||||
|
/// An array of network devices.
|
||||||
|
///
|
||||||
|
/// XDP programs can use this map to redirect packets to other network deviecs.
|
||||||
|
///
|
||||||
|
/// # Minimum kernel version
|
||||||
|
///
|
||||||
|
/// The minimum kernel version required to use this feature is 4.14.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::DevMap, programs::XdpContext};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: DevMap = DevMap::with_max_entries(1, 0);
|
||||||
|
///
|
||||||
|
/// #[xdp]
|
||||||
|
/// fn xdp(_ctx: XdpContext) -> i32 {
|
||||||
|
/// MAP.redirect(0, xdp_action::XDP_PASS as u64)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct DevMap {
|
||||||
|
def: UnsafeCell<bpf_map_def>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Sync for DevMap {}
|
||||||
|
|
||||||
|
impl DevMap {
|
||||||
|
/// Creates a [`DevMap`] with a set maximum number of elements.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{macros::map, maps::DevMap};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: DevMap = DevMap::with_max_entries(8, 0);
|
||||||
|
/// ```
|
||||||
|
pub const fn with_max_entries(max_entries: u32, flags: u32) -> DevMap {
|
||||||
|
DevMap {
|
||||||
|
def: UnsafeCell::new(bpf_map_def {
|
||||||
|
type_: BPF_MAP_TYPE_DEVMAP,
|
||||||
|
key_size: mem::size_of::<u32>() as u32,
|
||||||
|
value_size: mem::size_of::<bpf_devmap_val>() as u32,
|
||||||
|
max_entries,
|
||||||
|
map_flags: flags,
|
||||||
|
id: 0,
|
||||||
|
pinning: PinningType::None as u32,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`DevMap`] with a set maximum number of elements that can be pinned to the BPF
|
||||||
|
/// File System (bpffs).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{macros::map, maps::DevMap};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: DevMap = DevMap::pinned(8, 0);
|
||||||
|
/// ```
|
||||||
|
pub const fn pinned(max_entries: u32, flags: u32) -> DevMap {
|
||||||
|
DevMap {
|
||||||
|
def: UnsafeCell::new(bpf_map_def {
|
||||||
|
type_: BPF_MAP_TYPE_DEVMAP,
|
||||||
|
key_size: mem::size_of::<u32>() as u32,
|
||||||
|
value_size: mem::size_of::<bpf_devmap_val>() as u32,
|
||||||
|
max_entries,
|
||||||
|
map_flags: flags,
|
||||||
|
id: 0,
|
||||||
|
pinning: PinningType::ByName as u32,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the interface index at `index` in the array.
|
||||||
|
///
|
||||||
|
/// To actually redirect a packet, see [`DevMap::redirect`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{macros::map, maps::DevMap};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: DevMap = DevMap::with_max_entries(1, 0);
|
||||||
|
///
|
||||||
|
/// let target_if_index = MAP.get(0).target_if_index;
|
||||||
|
///
|
||||||
|
/// // redirect to if_index
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get(&self, index: u32) -> Option<DevMapValue> {
|
||||||
|
unsafe {
|
||||||
|
let value = bpf_map_lookup_elem(
|
||||||
|
self.def.get() as *mut _,
|
||||||
|
&index as *const _ as *const c_void,
|
||||||
|
);
|
||||||
|
NonNull::new(value as *mut bpf_devmap_val).map(|p| DevMapValue {
|
||||||
|
if_index: p.as_ref().ifindex,
|
||||||
|
// SAFETY: map writes use fd, map reads use id.
|
||||||
|
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6136
|
||||||
|
prog_id: NonZeroU32::new(p.as_ref().bpf_prog.id),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redirects the current packet on the interface at `index`.
|
||||||
|
///
|
||||||
|
/// The lower two bits of `flags` are used for the return code if the map lookup fails, which
|
||||||
|
/// can be used as the XDP program's return code if a CPU cannot be found.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::DevMap, programs::XdpContext};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: DevMap = DevMap::with_max_entries(8, 0);
|
||||||
|
///
|
||||||
|
/// #[xdp]
|
||||||
|
/// fn xdp(_ctx: XdpContext) -> u32 {
|
||||||
|
/// MAP.redirect(7, 0).unwrap_or(xdp_action::XDP_DROP)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn redirect(&self, index: u32, flags: u64) -> Result<u32, u32> {
|
||||||
|
try_redirect_map(&self.def, index, flags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
/// The value of a device map.
|
||||||
|
pub struct DevMapValue {
|
||||||
|
/// Target interface index to redirect to.
|
||||||
|
pub if_index: u32,
|
||||||
|
/// Chained XDP program ID.
|
||||||
|
pub prog_id: Option<NonZeroU32>,
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
use core::{cell::UnsafeCell, mem, num::NonZeroU32, ptr::NonNull};
|
||||||
|
|
||||||
|
use aya_bpf_bindings::bindings::bpf_devmap_val;
|
||||||
|
use aya_bpf_cty::c_void;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH},
|
||||||
|
helpers::bpf_map_lookup_elem,
|
||||||
|
maps::PinningType,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{dev_map::DevMapValue, try_redirect_map};
|
||||||
|
|
||||||
|
/// A map of network devices.
|
||||||
|
///
|
||||||
|
/// XDP programs can use this map to redirect packets to other network devices. It is similar to
|
||||||
|
/// [`DevMap`](super::DevMap), but is an hash map rather than an array. Keys do not need to be
|
||||||
|
/// contiguous nor start at zero, but there is a hashing cost to every lookup.
|
||||||
|
///
|
||||||
|
/// # Minimum kernel version
|
||||||
|
///
|
||||||
|
/// The minimum kernel version required to use this feature is 5.4.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::DevMapHash, programs::XdpContext};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: DevMapHash = DevMapHash::with_max_entries(1, 0);
|
||||||
|
///
|
||||||
|
/// #[xdp]
|
||||||
|
/// fn xdp(_ctx: XdpContext) -> i32 {
|
||||||
|
/// MAP.redirect(42, xdp_action::XDP_PASS as u64)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct DevMapHash {
|
||||||
|
def: UnsafeCell<bpf_map_def>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Sync for DevMapHash {}
|
||||||
|
|
||||||
|
impl DevMapHash {
|
||||||
|
/// Creates a [`DevMapHash`] with a set maximum number of elements.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{macros::map, maps::DevMapHash};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: DevMapHash = DevMapHash::with_max_entries(8, 0);
|
||||||
|
/// ```
|
||||||
|
pub const fn with_max_entries(max_entries: u32, flags: u32) -> DevMapHash {
|
||||||
|
DevMapHash {
|
||||||
|
def: UnsafeCell::new(bpf_map_def {
|
||||||
|
type_: BPF_MAP_TYPE_DEVMAP_HASH,
|
||||||
|
key_size: mem::size_of::<u32>() as u32,
|
||||||
|
value_size: mem::size_of::<bpf_devmap_val>() as u32,
|
||||||
|
max_entries,
|
||||||
|
map_flags: flags,
|
||||||
|
id: 0,
|
||||||
|
pinning: PinningType::None as u32,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`DevMapHash`] with a set maximum number of elements that can be pinned to the BPF
|
||||||
|
/// File System (bpffs).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{macros::map, maps::DevMapHash};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: DevMapHash = DevMapHash::pinned(8, 0);
|
||||||
|
/// ```
|
||||||
|
pub const fn pinned(max_entries: u32, flags: u32) -> DevMapHash {
|
||||||
|
DevMapHash {
|
||||||
|
def: UnsafeCell::new(bpf_map_def {
|
||||||
|
type_: BPF_MAP_TYPE_DEVMAP_HASH,
|
||||||
|
key_size: mem::size_of::<u32>() as u32,
|
||||||
|
value_size: mem::size_of::<bpf_devmap_val>() as u32,
|
||||||
|
max_entries,
|
||||||
|
map_flags: flags,
|
||||||
|
id: 0,
|
||||||
|
pinning: PinningType::ByName as u32,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the interface index with `key` in the map.
|
||||||
|
///
|
||||||
|
/// To actually redirect a packet, see [`DevMapHash::redirect`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{macros::map, maps::DevMapHash};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: DevMapHash = DevMapHash::with_max_entries(1, 0);
|
||||||
|
///
|
||||||
|
/// let target_if_index = MAP.get(42).target_if_index;
|
||||||
|
///
|
||||||
|
/// // redirect to ifindex
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get(&self, key: u32) -> Option<DevMapValue> {
|
||||||
|
unsafe {
|
||||||
|
let value =
|
||||||
|
bpf_map_lookup_elem(self.def.get() as *mut _, &key as *const _ as *const c_void);
|
||||||
|
NonNull::new(value as *mut bpf_devmap_val).map(|p| DevMapValue {
|
||||||
|
if_index: p.as_ref().ifindex,
|
||||||
|
// SAFETY: map writes use fd, map reads use id.
|
||||||
|
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6136
|
||||||
|
prog_id: NonZeroU32::new(p.as_ref().bpf_prog.id),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redirects the current packet on the interface at `key`.
|
||||||
|
///
|
||||||
|
/// The lower two bits of `flags` are used for the return code if the map lookup fails, which
|
||||||
|
/// can be used as the XDP program's return code if a CPU cannot be found.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::DevMapHash, programs::XdpContext};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static MAP: DevMapHash = DevMapHash::with_max_entries(8, 0);
|
||||||
|
///
|
||||||
|
/// #[xdp]
|
||||||
|
/// fn xdp(_ctx: XdpContext) -> u32 {
|
||||||
|
/// MAP.redirect(7, 0).unwrap_or(xdp_action::XDP_DROP)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn redirect(&self, key: u32, flags: u64) -> Result<u32, u32> {
|
||||||
|
try_redirect_map(&self.def, key, flags)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
mod cpu_map;
|
||||||
|
mod dev_map;
|
||||||
|
mod dev_map_hash;
|
||||||
|
mod xsk_map;
|
||||||
|
|
||||||
|
use core::cell::UnsafeCell;
|
||||||
|
|
||||||
|
use aya_bpf_bindings::{
|
||||||
|
bindings::{bpf_map_def, xdp_action::XDP_REDIRECT},
|
||||||
|
helpers::bpf_redirect_map,
|
||||||
|
};
|
||||||
|
pub use cpu_map::CpuMap;
|
||||||
|
pub use dev_map::DevMap;
|
||||||
|
pub use dev_map_hash::DevMapHash;
|
||||||
|
pub use xsk_map::XskMap;
|
||||||
|
|
||||||
|
/// Wrapper aroung the `bpf_redirect_map` function.
|
||||||
|
///
|
||||||
|
/// # Return value
|
||||||
|
///
|
||||||
|
/// - `Ok(XDP_REDIRECT)` on success.
|
||||||
|
/// - `Err(_)` of the lowest two bits of `flags` on failure.
|
||||||
|
#[inline(always)]
|
||||||
|
fn try_redirect_map(def: &UnsafeCell<bpf_map_def>, key: u32, flags: u64) -> Result<u32, u32> {
|
||||||
|
// Return XDP_REDIRECT on success, or the value of the two lower bits of the flags argument on
|
||||||
|
// error. Thus I have no idea why it returns a long (i64) instead of something saner, hence the
|
||||||
|
// unsigned_abs.
|
||||||
|
let ret = unsafe { bpf_redirect_map(def.get() as *mut _, key.into(), flags) };
|
||||||
|
match ret.unsigned_abs() as u32 {
|
||||||
|
XDP_REDIRECT => Ok(XDP_REDIRECT),
|
||||||
|
ret => Err(ret),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
use core::{cell::UnsafeCell, mem, ptr::NonNull};
|
||||||
|
|
||||||
|
use aya_bpf_bindings::bindings::bpf_xdp_sock;
|
||||||
|
use aya_bpf_cty::c_void;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_XSKMAP},
|
||||||
|
helpers::bpf_map_lookup_elem,
|
||||||
|
maps::PinningType,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::try_redirect_map;
|
||||||
|
|
||||||
|
/// An array of AF_XDP sockets.
|
||||||
|
///
|
||||||
|
/// XDP programs can use this map to redirect packets to a target AF_XDP socket using the
|
||||||
|
/// `XDP_REDIRECT` action.
|
||||||
|
///
|
||||||
|
/// # Minimum kernel version
|
||||||
|
///
|
||||||
|
/// The minimum kernel version required to use this feature is 4.18.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::XskMap, programs::XdpContext};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static SOCKS: XskMap = XskMap::with_max_entries(8, 0);
|
||||||
|
///
|
||||||
|
/// #[xdp]
|
||||||
|
/// fn xdp(ctx, XdpContext) -> i32 {
|
||||||
|
/// let queue_id = unsafe { (*ctx.ctx).rx_queue_index };
|
||||||
|
/// MAP.redirect(queue_id, xdp_action::XDP_DROP as u64)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Queue management
|
||||||
|
///
|
||||||
|
/// Packets received on a RX queue can only be redirected to sockets bound on the same queue. Most
|
||||||
|
/// hardware NICs have multiple RX queue to spread the load across multiple CPU cores using RSS.
|
||||||
|
///
|
||||||
|
/// Three strategies are possible:
|
||||||
|
///
|
||||||
|
/// - Reduce the RX queue count to a single one. This option is great for development, but is
|
||||||
|
/// detrimental for performance as the single CPU core recieving packets will get overwhelmed.
|
||||||
|
/// Setting the queue count for a NIC can be achieved using `ethtool -L <ifname> combined 1`.
|
||||||
|
/// - Create a socket for every RX queue. Most modern NICs will have an RX queue per CPU thread, so
|
||||||
|
/// a socket per CPU thread is best for performance. To dynamically size the map depending on the
|
||||||
|
/// recieve queue count, see the userspace documentation of `CpuMap`.
|
||||||
|
/// - Create a single socket and use a [`CpuMap`](super::CpuMap) to redirect the packet to the
|
||||||
|
/// correct CPU core. This way, the packet is sent to another CPU, and a chained XDP program can
|
||||||
|
/// the redirect to the AF_XDP socket. Using a single socket simplifies the userspace code but
|
||||||
|
/// will not perform great unless not a lot of traffic is redirected to the socket. Regular
|
||||||
|
/// traffic however will not be impacted, contrary to reducing the queue count.
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct XskMap {
|
||||||
|
def: UnsafeCell<bpf_map_def>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Sync for XskMap {}
|
||||||
|
|
||||||
|
impl XskMap {
|
||||||
|
/// Creates a [`XskMap`] with a set maximum number of elements.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{macros::map, maps::XskMap};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static SOCKS: XskMap::with_max_entries(8, 0);
|
||||||
|
/// ```
|
||||||
|
pub const fn with_max_entries(max_entries: u32, flags: u32) -> XskMap {
|
||||||
|
XskMap {
|
||||||
|
def: UnsafeCell::new(bpf_map_def {
|
||||||
|
type_: BPF_MAP_TYPE_XSKMAP,
|
||||||
|
key_size: mem::size_of::<u32>() as u32,
|
||||||
|
value_size: mem::size_of::<u32>() as u32,
|
||||||
|
max_entries,
|
||||||
|
map_flags: flags,
|
||||||
|
id: 0,
|
||||||
|
pinning: PinningType::None as u32,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`XskMap`] with a set maximum number of elements that can be pinned to the BPF
|
||||||
|
/// filesystem (bpffs).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{macros::map, maps::XskMap};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static SOCKS: XskMap::pinned(8, 0);
|
||||||
|
/// ```
|
||||||
|
pub const fn pinned(max_entries: u32, flags: u32) -> XskMap {
|
||||||
|
XskMap {
|
||||||
|
def: UnsafeCell::new(bpf_map_def {
|
||||||
|
type_: BPF_MAP_TYPE_XSKMAP,
|
||||||
|
key_size: mem::size_of::<u32>() as u32,
|
||||||
|
value_size: mem::size_of::<u32>() as u32,
|
||||||
|
max_entries,
|
||||||
|
map_flags: flags,
|
||||||
|
id: 0,
|
||||||
|
pinning: PinningType::ByName as u32,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the queue to which the socket is bound at `index` in the array.
|
||||||
|
///
|
||||||
|
/// To actually redirect a packet, see [`XskMap::redirect`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{macros::map, maps::XskMap};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static SOCKS: XskMap = XskMap::with_max_entries(8, 0);
|
||||||
|
///
|
||||||
|
/// let queue_id = SOCKS.get(0);
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get(&self, index: u32) -> Option<u32> {
|
||||||
|
unsafe {
|
||||||
|
let value = bpf_map_lookup_elem(
|
||||||
|
self.def.get() as *mut _,
|
||||||
|
&index as *const _ as *const c_void,
|
||||||
|
);
|
||||||
|
NonNull::new(value as *mut bpf_xdp_sock).map(|p| p.as_ref().queue_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redirects the current packet to the AF_XDP socket at `index`.
|
||||||
|
///
|
||||||
|
/// The lower two bits of `flags` are used for the return code if the map lookup fails, which
|
||||||
|
/// can be used as the XDP program's return code if a matching socket cannot be found.
|
||||||
|
///
|
||||||
|
/// However, if the socket at `index` is bound to a RX queue which is not the current RX queue,
|
||||||
|
/// the packet will be dropped.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use aya_bpf::{bindings::xdp_action, macros::{map, xdp}, maps::XskMap, programs::XdpContext};
|
||||||
|
///
|
||||||
|
/// #[map]
|
||||||
|
/// static SOCKS: XskMap = XskMap::with_max_entries(8, 0);
|
||||||
|
///
|
||||||
|
/// #[xdp]
|
||||||
|
/// fn xdp(ctx, XdpContext) -> u32 {
|
||||||
|
/// let queue_id = unsafe { (*ctx.ctx).rx_queue_index };
|
||||||
|
/// MAP.redirect(queue_id, 0).unwrap_or(xdp_action::XDP_DROP)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn redirect(&self, index: u32, flags: u64) -> Result<u32, u32> {
|
||||||
|
try_redirect_map(&self.def, index, flags)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use aya_bpf::{
|
||||||
|
bindings::xdp_action,
|
||||||
|
macros::{map, xdp},
|
||||||
|
maps::{Array, CpuMap, DevMap, DevMapHash, XskMap},
|
||||||
|
programs::XdpContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[map]
|
||||||
|
static SOCKS: XskMap = XskMap::with_max_entries(1, 0);
|
||||||
|
#[map]
|
||||||
|
static DEVS: DevMap = DevMap::with_max_entries(1, 0);
|
||||||
|
#[map]
|
||||||
|
static DEVS_HASH: DevMapHash = DevMapHash::with_max_entries(1, 0);
|
||||||
|
#[map]
|
||||||
|
static CPUS: CpuMap = CpuMap::with_max_entries(1, 0);
|
||||||
|
|
||||||
|
/// Hits of a probe, used to test program chaining through CpuMap/DevMap.
|
||||||
|
/// The first slot counts how many times the "raw" xdp program got executed, while the second slot
|
||||||
|
/// counts how many times the map programs got executed.
|
||||||
|
/// This allows the test harness to assert that a specific step got executed.
|
||||||
|
#[map]
|
||||||
|
static mut HITS: Array<u32> = Array::with_max_entries(2, 0);
|
||||||
|
|
||||||
|
#[xdp]
|
||||||
|
pub fn redirect_sock(_ctx: XdpContext) -> u32 {
|
||||||
|
SOCKS.redirect(0, 0).unwrap_or(xdp_action::XDP_ABORTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[xdp]
|
||||||
|
pub fn redirect_dev(_ctx: XdpContext) -> u32 {
|
||||||
|
inc_hit(0);
|
||||||
|
DEVS.redirect(0, 0).unwrap_or(xdp_action::XDP_ABORTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[xdp]
|
||||||
|
pub fn redirect_dev_hash(_ctx: XdpContext) -> u32 {
|
||||||
|
inc_hit(0);
|
||||||
|
DEVS_HASH.redirect(10, 0).unwrap_or(xdp_action::XDP_ABORTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[xdp]
|
||||||
|
pub fn redirect_cpu(_ctx: XdpContext) -> u32 {
|
||||||
|
inc_hit(0);
|
||||||
|
CPUS.redirect(0, 0).unwrap_or(xdp_action::XDP_ABORTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[xdp(map = "cpumap")]
|
||||||
|
pub fn redirect_cpu_chain(_ctx: XdpContext) -> u32 {
|
||||||
|
inc_hit(1);
|
||||||
|
xdp_action::XDP_PASS
|
||||||
|
}
|
||||||
|
|
||||||
|
#[xdp(map = "devmap")]
|
||||||
|
pub fn redirect_dev_chain(_ctx: XdpContext) -> u32 {
|
||||||
|
inc_hit(1);
|
||||||
|
xdp_action::XDP_PASS
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn inc_hit(index: u32) {
|
||||||
|
if let Some(hit) = unsafe { HITS.get_ptr_mut(index) } {
|
||||||
|
unsafe { *hit += 1 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use aya_bpf::{bindings::xdp_action::XDP_PASS, macros::xdp, programs::XdpContext};
|
||||||
|
|
||||||
|
macro_rules! probe {
|
||||||
|
($name:ident, ($($arg:ident $(= $value:literal)?),*) ) => {
|
||||||
|
#[xdp($($arg $(= $value)?),*)]
|
||||||
|
pub fn $name(_ctx: XdpContext) -> u32 {
|
||||||
|
XDP_PASS
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
probe!(xdp_plain, ());
|
||||||
|
probe!(xdp_frags, (frags));
|
||||||
|
probe!(xdp_cpumap, (map = "cpumap"));
|
||||||
|
probe!(xdp_devmap, (map = "devmap"));
|
||||||
|
probe!(xdp_frags_cpumap, (frags, map = "cpumap"));
|
||||||
|
probe!(xdp_frags_devmap, (frags, map = "devmap"));
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
use std::{net::UdpSocket, time::Duration};
|
||||||
|
|
||||||
|
use aya::{
|
||||||
|
maps::{Array, CpuMap},
|
||||||
|
programs::{Xdp, XdpFlags},
|
||||||
|
Bpf,
|
||||||
|
};
|
||||||
|
use object::{Object, ObjectSection, ObjectSymbol, SymbolSection};
|
||||||
|
|
||||||
|
use crate::utils::NetNsGuard;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prog_sections() {
|
||||||
|
let obj_file = object::File::parse(crate::XDP_SEC).unwrap();
|
||||||
|
|
||||||
|
ensure_symbol(&obj_file, "xdp", "xdp_plain");
|
||||||
|
ensure_symbol(&obj_file, "xdp.frags", "xdp_frags");
|
||||||
|
ensure_symbol(&obj_file, "xdp/cpumap", "xdp_cpumap");
|
||||||
|
ensure_symbol(&obj_file, "xdp/devmap", "xdp_devmap");
|
||||||
|
ensure_symbol(&obj_file, "xdp.frags/cpumap", "xdp_frags_cpumap");
|
||||||
|
ensure_symbol(&obj_file, "xdp.frags/devmap", "xdp_frags_devmap");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn ensure_symbol(obj_file: &object::File, sec_name: &str, sym_name: &str) {
|
||||||
|
let sec = obj_file.section_by_name(sec_name).unwrap_or_else(|| {
|
||||||
|
let secs = obj_file
|
||||||
|
.sections()
|
||||||
|
.flat_map(|sec| sec.name().ok().map(|name| name.to_owned()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
panic!("section {sec_name} not found. available sections: {secs:?}");
|
||||||
|
});
|
||||||
|
let sec = SymbolSection::Section(sec.index());
|
||||||
|
|
||||||
|
let syms = obj_file
|
||||||
|
.symbols()
|
||||||
|
.filter(|sym| sym.section() == sec)
|
||||||
|
.filter_map(|sym| sym.name().ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
assert!(
|
||||||
|
syms.contains(&sym_name),
|
||||||
|
"symbol not found. available symbols in section: {syms:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_load() {
|
||||||
|
let bpf = Bpf::load(crate::XDP_SEC).unwrap();
|
||||||
|
|
||||||
|
bpf.program("xdp_plain").unwrap();
|
||||||
|
bpf.program("xdp_frags").unwrap();
|
||||||
|
bpf.program("xdp_cpumap").unwrap();
|
||||||
|
bpf.program("xdp_devmap").unwrap();
|
||||||
|
bpf.program("xdp_frags_cpumap").unwrap();
|
||||||
|
bpf.program("xdp_frags_devmap").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cpumap_chain() {
|
||||||
|
let _netns = NetNsGuard::new();
|
||||||
|
|
||||||
|
let mut bpf = Bpf::load(crate::REDIRECT).unwrap();
|
||||||
|
|
||||||
|
// Load our cpumap and our canary map
|
||||||
|
let mut cpus: CpuMap<_> = bpf.take_map("CPUS").unwrap().try_into().unwrap();
|
||||||
|
let hits: Array<_, u32> = bpf.take_map("HITS").unwrap().try_into().unwrap();
|
||||||
|
|
||||||
|
let xdp_chain_fd = {
|
||||||
|
// Load the chained program to run on the target CPU
|
||||||
|
let xdp: &mut Xdp = bpf
|
||||||
|
.program_mut("redirect_cpu_chain")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
xdp.load().unwrap();
|
||||||
|
xdp.fd().unwrap()
|
||||||
|
};
|
||||||
|
cpus.set(0, 2048, Some(xdp_chain_fd), 0).unwrap();
|
||||||
|
|
||||||
|
// Load the main program
|
||||||
|
let xdp: &mut Xdp = bpf.program_mut("redirect_cpu").unwrap().try_into().unwrap();
|
||||||
|
xdp.load().unwrap();
|
||||||
|
xdp.attach("lo", XdpFlags::default()).unwrap();
|
||||||
|
|
||||||
|
let sock = UdpSocket::bind("127.0.0.1:1777").unwrap();
|
||||||
|
sock.set_read_timeout(Some(Duration::from_millis(1)))
|
||||||
|
.unwrap();
|
||||||
|
sock.send_to(b"hello cpumap", "127.0.0.1:1777").unwrap();
|
||||||
|
|
||||||
|
// Read back the packet to ensure it wenth through the entire network stack, including our two
|
||||||
|
// probes.
|
||||||
|
let mut buf = vec![0u8; 1000];
|
||||||
|
let n = sock.recv(&mut buf).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(&buf[..n], b"hello cpumap");
|
||||||
|
assert_eq!(hits.get(&0, 0).unwrap(), 1);
|
||||||
|
assert_eq!(hits.get(&1, 0).unwrap(), 1);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue