//! 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, } 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 { 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 { 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 { 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 { 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(::Id), NlLinkId(::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 for FdLink { type Error = LinkError; fn try_from(value: XdpLink) -> Result { if let XdpLinkInner::FdLink(fd) = value.into_inner() { Ok(fd) } else { Err(LinkError::InvalidLink) } } } impl TryFrom for XdpLink { type Error = LinkError; fn try_from(fd_link: FdLink) -> Result { // unwrap of fd_link.fd will not panic since it's only None when being dropped. let info = bpf_link_get_info_by_fd(fd_link.fd.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 );