mirror of https://github.com/aya-rs/aya
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
308 lines
9.5 KiB
Rust
308 lines
9.5 KiB
Rust
//! eXpress Data Path (XDP) programs.
|
|
|
|
use crate::{sys::SyscallError, util::KernelVersion};
|
|
use bitflags;
|
|
use libc::if_nametoindex;
|
|
use std::{
|
|
convert::TryFrom,
|
|
ffi::CString,
|
|
hash::Hash,
|
|
io,
|
|
os::fd::{AsFd as _, AsRawFd as _, RawFd},
|
|
};
|
|
use thiserror::Error;
|
|
|
|
use crate::{
|
|
generated::{
|
|
bpf_attach_type, bpf_link_type, bpf_prog_type, XDP_FLAGS_DRV_MODE, XDP_FLAGS_HW_MODE,
|
|
XDP_FLAGS_REPLACE, XDP_FLAGS_SKB_MODE, XDP_FLAGS_UPDATE_IF_NOEXIST,
|
|
},
|
|
programs::{
|
|
define_link_wrapper, load_program, FdLink, Link, LinkError, ProgramData, ProgramError,
|
|
},
|
|
sys::{bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, netlink_set_xdp_fd},
|
|
};
|
|
|
|
/// The type returned when attaching an [`Xdp`] program fails on kernels `< 5.9`.
|
|
#[derive(Debug, Error)]
|
|
pub enum XdpError {
|
|
/// netlink error while attaching XDP program
|
|
#[error("netlink error while attaching XDP program")]
|
|
NetlinkError {
|
|
/// the [`io::Error`] from the netlink call
|
|
#[source]
|
|
io_error: io::Error,
|
|
},
|
|
}
|
|
|
|
bitflags::bitflags! {
|
|
/// Flags passed to [`Xdp::attach()`].
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
pub struct XdpFlags: u32 {
|
|
/// Skb mode.
|
|
const SKB_MODE = XDP_FLAGS_SKB_MODE;
|
|
/// Driver mode.
|
|
const DRV_MODE = XDP_FLAGS_DRV_MODE;
|
|
/// Hardware mode.
|
|
const HW_MODE = XDP_FLAGS_HW_MODE;
|
|
/// Replace a previously attached XDP program.
|
|
const REPLACE = XDP_FLAGS_REPLACE;
|
|
/// Only attach if there isn't another XDP program already attached.
|
|
const UPDATE_IF_NOEXIST = XDP_FLAGS_UPDATE_IF_NOEXIST;
|
|
}
|
|
}
|
|
|
|
/// An XDP program.
|
|
///
|
|
/// eXpress Data Path (XDP) programs can be attached to the very early stages of network
|
|
/// processing, where they can apply custom packet processing logic. When supported by the
|
|
/// underlying network driver, XDP programs can execute directly on network cards, greatly
|
|
/// reducing CPU load.
|
|
///
|
|
/// # Minimum kernel version
|
|
///
|
|
/// The minimum kernel version required to use this feature is 4.8.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```no_run
|
|
/// # let mut bpf = Bpf::load_file("ebpf_programs.o")?;
|
|
/// use aya::{Bpf, programs::{Xdp, XdpFlags}};
|
|
///
|
|
/// let program: &mut Xdp = bpf.program_mut("intercept_packets").unwrap().try_into()?;
|
|
/// program.attach("eth0", XdpFlags::default())?;
|
|
/// # Ok::<(), aya::BpfError>(())
|
|
/// ```
|
|
#[derive(Debug)]
|
|
#[doc(alias = "BPF_PROG_TYPE_XDP")]
|
|
pub struct Xdp {
|
|
pub(crate) data: ProgramData<XdpLink>,
|
|
}
|
|
|
|
impl Xdp {
|
|
/// Loads the program inside the kernel.
|
|
pub fn load(&mut self) -> Result<(), ProgramError> {
|
|
self.data.expected_attach_type = Some(bpf_attach_type::BPF_XDP);
|
|
load_program(bpf_prog_type::BPF_PROG_TYPE_XDP, &mut self.data)
|
|
}
|
|
|
|
/// Attaches the program to the given `interface`.
|
|
///
|
|
/// The returned value can be used to detach, see [Xdp::detach].
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// If the given `interface` does not exist
|
|
/// [`ProgramError::UnknownInterface`] is returned.
|
|
///
|
|
/// When attaching fails, [`ProgramError::SyscallError`] is returned for
|
|
/// kernels `>= 5.9.0`, and instead
|
|
/// [`XdpError::NetlinkError`] is returned for older
|
|
/// kernels.
|
|
pub fn attach(&mut self, interface: &str, flags: XdpFlags) -> Result<XdpLinkId, ProgramError> {
|
|
let c_interface = CString::new(interface).unwrap();
|
|
let if_index = unsafe { if_nametoindex(c_interface.as_ptr()) };
|
|
if if_index == 0 {
|
|
return Err(ProgramError::UnknownInterface {
|
|
name: interface.to_string(),
|
|
});
|
|
}
|
|
self.attach_to_if_index(if_index, flags)
|
|
}
|
|
|
|
/// Attaches the program to the given interface index.
|
|
///
|
|
/// The returned value can be used to detach, see [Xdp::detach].
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// When attaching fails, [`ProgramError::SyscallError`] is returned for
|
|
/// kernels `>= 5.9.0`, and instead
|
|
/// [`XdpError::NetlinkError`] is returned for older
|
|
/// kernels.
|
|
pub fn attach_to_if_index(
|
|
&mut self,
|
|
if_index: u32,
|
|
flags: XdpFlags,
|
|
) -> Result<XdpLinkId, ProgramError> {
|
|
let prog_fd = self.fd()?;
|
|
let prog_fd = prog_fd.as_fd();
|
|
let prog_fd = prog_fd.as_raw_fd();
|
|
let if_index = if_index as RawFd;
|
|
|
|
if KernelVersion::current().unwrap() >= KernelVersion::new(5, 9, 0) {
|
|
let link_fd = bpf_link_create(
|
|
prog_fd,
|
|
if_index,
|
|
bpf_attach_type::BPF_XDP,
|
|
None,
|
|
flags.bits(),
|
|
)
|
|
.map_err(|(_, io_error)| SyscallError {
|
|
call: "bpf_link_create",
|
|
io_error,
|
|
})?;
|
|
self.data
|
|
.links
|
|
.insert(XdpLink::new(XdpLinkInner::FdLink(FdLink::new(link_fd))))
|
|
} 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::new(XdpLinkInner::NlLink(NlLink {
|
|
if_index,
|
|
prog_fd,
|
|
flags,
|
|
})))
|
|
}
|
|
}
|
|
|
|
/// Detaches the program.
|
|
///
|
|
/// See [Xdp::attach].
|
|
pub fn detach(&mut self, link_id: XdpLinkId) -> Result<(), ProgramError> {
|
|
self.data.links.remove(link_id)
|
|
}
|
|
|
|
/// Takes ownership of the link referenced by the provided link_id.
|
|
///
|
|
/// The link will be detached on `Drop` and the caller is now responsible
|
|
/// for managing its lifetime.
|
|
pub fn take_link(&mut self, link_id: XdpLinkId) -> Result<XdpLink, ProgramError> {
|
|
self.data.take_link(link_id)
|
|
}
|
|
|
|
/// Atomically replaces the program referenced by the provided link.
|
|
///
|
|
/// Ownership of the link will transfer to this program.
|
|
pub fn attach_to_link(&mut self, link: XdpLink) -> Result<XdpLinkId, ProgramError> {
|
|
let prog_fd = self.fd()?;
|
|
let prog_fd = prog_fd.as_fd();
|
|
let prog_fd = prog_fd.as_raw_fd();
|
|
match link.into_inner() {
|
|
XdpLinkInner::FdLink(fd_link) => {
|
|
let link_fd = fd_link.fd;
|
|
bpf_link_update(link_fd.as_fd(), prog_fd, None, 0).map_err(|(_, io_error)| {
|
|
SyscallError {
|
|
call: "bpf_link_update",
|
|
io_error,
|
|
}
|
|
})?;
|
|
|
|
self.data
|
|
.links
|
|
.insert(XdpLink::new(XdpLinkInner::FdLink(FdLink::new(link_fd))))
|
|
}
|
|
XdpLinkInner::NlLink(nl_link) => {
|
|
let if_index = nl_link.if_index;
|
|
let old_prog_fd = nl_link.prog_fd;
|
|
let flags = nl_link.flags;
|
|
let replace_flags = flags | XdpFlags::REPLACE;
|
|
unsafe {
|
|
netlink_set_xdp_fd(if_index, prog_fd, Some(old_prog_fd), replace_flags.bits())
|
|
.map_err(|io_error| XdpError::NetlinkError { io_error })?;
|
|
}
|
|
|
|
self.data
|
|
.links
|
|
.insert(XdpLink::new(XdpLinkInner::NlLink(NlLink {
|
|
if_index,
|
|
prog_fd,
|
|
flags,
|
|
})))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct NlLink {
|
|
if_index: i32,
|
|
prog_fd: RawFd,
|
|
flags: XdpFlags,
|
|
}
|
|
|
|
impl Link for NlLink {
|
|
type Id = (i32, RawFd);
|
|
|
|
fn id(&self) -> Self::Id {
|
|
(self.if_index, self.prog_fd)
|
|
}
|
|
|
|
fn detach(self) -> Result<(), ProgramError> {
|
|
let flags = if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) {
|
|
self.flags.bits() | XDP_FLAGS_REPLACE
|
|
} else {
|
|
self.flags.bits()
|
|
};
|
|
let _ = unsafe { netlink_set_xdp_fd(self.if_index, -1, Some(self.prog_fd), flags) };
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Hash, Eq, PartialEq)]
|
|
pub(crate) enum XdpLinkIdInner {
|
|
FdLinkId(<FdLink as Link>::Id),
|
|
NlLinkId(<NlLink as Link>::Id),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum XdpLinkInner {
|
|
FdLink(FdLink),
|
|
NlLink(NlLink),
|
|
}
|
|
|
|
impl Link for XdpLinkInner {
|
|
type Id = XdpLinkIdInner;
|
|
|
|
fn id(&self) -> Self::Id {
|
|
match self {
|
|
Self::FdLink(link) => XdpLinkIdInner::FdLinkId(link.id()),
|
|
Self::NlLink(link) => XdpLinkIdInner::NlLinkId(link.id()),
|
|
}
|
|
}
|
|
|
|
fn detach(self) -> Result<(), ProgramError> {
|
|
match self {
|
|
Self::FdLink(link) => link.detach(),
|
|
Self::NlLink(link) => link.detach(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<XdpLink> for FdLink {
|
|
type Error = LinkError;
|
|
|
|
fn try_from(value: XdpLink) -> Result<Self, Self::Error> {
|
|
if let XdpLinkInner::FdLink(fd) = value.into_inner() {
|
|
Ok(fd)
|
|
} else {
|
|
Err(LinkError::InvalidLink)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<FdLink> for XdpLink {
|
|
type Error = LinkError;
|
|
|
|
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.
|
|
let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd())?;
|
|
if info.type_ == (bpf_link_type::BPF_LINK_TYPE_XDP as u32) {
|
|
return Ok(Self::new(XdpLinkInner::FdLink(fd_link)));
|
|
}
|
|
Err(LinkError::InvalidLink)
|
|
}
|
|
}
|
|
|
|
define_link_wrapper!(
|
|
/// The link used by [Xdp] programs.
|
|
XdpLink,
|
|
/// The type returned by [Xdp::attach]. Can be passed to [Xdp::detach].
|
|
XdpLinkId,
|
|
XdpLinkInner,
|
|
XdpLinkIdInner
|
|
);
|