// `rustdoc` is buggy, claiming that we have some links to private items // when they are actually public. #![allow(rustdoc::private_intra_doc_links)] use std::ffi::c_void; use std::ffi::CStr; use std::ffi::OsStr; use std::marker::PhantomData; use std::mem; use std::mem::size_of; use std::mem::size_of_val; use std::mem::transmute; use std::ops::Deref; use std::os::unix::ffi::OsStrExt as _; use std::os::unix::io::AsFd; use std::os::unix::io::AsRawFd; use std::os::unix::io::BorrowedFd; use std::os::unix::io::FromRawFd; use std::os::unix::io::OwnedFd; use std::path::Path; use std::ptr; use std::ptr::NonNull; use std::slice; use libbpf_sys::bpf_func_id; use crate::util; use crate::util::validate_bpf_ret; use crate::AsRawLibbpf; use crate::Error; use crate::ErrorExt as _; use crate::Link; use crate::Mut; use crate::Result; /// Options to optionally be provided when attaching to a uprobe. #[derive(Clone, Debug, Default)] pub struct UprobeOpts { /// Offset of kernel reference counted USDT semaphore. pub ref_ctr_offset: usize, /// Custom user-provided value accessible through `bpf_get_attach_cookie`. pub cookie: u64, /// uprobe is return probe, invoked at function return time. pub retprobe: bool, /// Function name to attach to. /// /// Could be an unqualified ("abc") or library-qualified "abc@LIBXYZ" name. /// To specify function entry, `func_name` should be set while `func_offset` /// argument to should be 0. To trace an offset within a function, specify /// `func_name` and use `func_offset` argument to specify offset within the /// function. Shared library functions must specify the shared library /// binary_path. pub func_name: String, #[doc(hidden)] pub _non_exhaustive: (), } /// Options to optionally be provided when attaching to a USDT. #[derive(Clone, Debug, Default)] pub struct UsdtOpts { /// Custom user-provided value accessible through `bpf_usdt_cookie`. pub cookie: u64, #[doc(hidden)] pub _non_exhaustive: (), } impl From<UsdtOpts> for libbpf_sys::bpf_usdt_opts { fn from(opts: UsdtOpts) -> Self { let UsdtOpts { cookie, _non_exhaustive, } = opts; #[allow(clippy::needless_update)] libbpf_sys::bpf_usdt_opts { sz: size_of::<Self>() as _, usdt_cookie: cookie, // bpf_usdt_opts might have padding fields on some platform ..Default::default() } } } /// Options to optionally be provided when attaching to a tracepoint. #[derive(Clone, Debug, Default)] pub struct TracepointOpts { /// Custom user-provided value accessible through `bpf_get_attach_cookie`. pub cookie: u64, #[doc(hidden)] pub _non_exhaustive: (), } impl From<TracepointOpts> for libbpf_sys::bpf_tracepoint_opts { fn from(opts: TracepointOpts) -> Self { let TracepointOpts { cookie, _non_exhaustive, } = opts; #[allow(clippy::needless_update)] libbpf_sys::bpf_tracepoint_opts { sz: size_of::<Self>() as _, bpf_cookie: cookie, // bpf_tracepoint_opts might have padding fields on some platform ..Default::default() } } } /// An immutable parsed but not yet loaded BPF program. pub type OpenProgram<'obj> = OpenProgramImpl<'obj>; /// A mutable parsed but not yet loaded BPF program. pub type OpenProgramMut<'obj> = OpenProgramImpl<'obj, Mut>; /// Represents a parsed but not yet loaded BPF program. /// /// This object exposes operations that need to happen before the program is loaded. #[derive(Debug)] #[repr(transparent)] pub struct OpenProgramImpl<'obj, T = ()> { ptr: NonNull<libbpf_sys::bpf_program>, _phantom: PhantomData<&'obj T>, } // TODO: Document variants. #[allow(missing_docs)] impl<'obj> OpenProgram<'obj> { /// Create a new [`OpenProgram`] from a ptr to a `libbpf_sys::bpf_program`. pub fn new(prog: &'obj libbpf_sys::bpf_program) -> Self { // SAFETY: We inferred the address from a reference, which is always // valid. Self { ptr: unsafe { NonNull::new_unchecked(prog as *const _ as *mut _) }, _phantom: PhantomData, } } // The `ProgramType` of this `OpenProgram`. pub fn prog_type(&self) -> ProgramType { ProgramType::from(unsafe { libbpf_sys::bpf_program__type(self.ptr.as_ptr()) }) } /// Retrieve the name of this `OpenProgram`. pub fn name(&self) -> &OsStr { let name_ptr = unsafe { libbpf_sys::bpf_program__name(self.ptr.as_ptr()) }; let name_c_str = unsafe { CStr::from_ptr(name_ptr) }; // SAFETY: `bpf_program__name` always returns a non-NULL pointer. OsStr::from_bytes(name_c_str.to_bytes()) } /// Retrieve the name of the section this `OpenProgram` belongs to. pub fn section(&self) -> &OsStr { // SAFETY: The program is always valid. let p = unsafe { libbpf_sys::bpf_program__section_name(self.ptr.as_ptr()) }; // SAFETY: `bpf_program__section_name` will always return a non-NULL // pointer. let section_c_str = unsafe { CStr::from_ptr(p) }; let section = OsStr::from_bytes(section_c_str.to_bytes()); section } /// Returns the number of instructions that form the program. /// /// Note: Keep in mind, libbpf can modify the program's instructions /// and consequently its instruction count, as it processes the BPF object file. /// So [`OpenProgram::insn_cnt`] and [`Program::insn_cnt`] may return different values. pub fn insn_cnt(&self) -> usize { unsafe { libbpf_sys::bpf_program__insn_cnt(self.ptr.as_ptr()) as usize } } /// Gives read-only access to BPF program's underlying BPF instructions. /// /// Keep in mind, libbpf can modify and append/delete BPF program's /// instructions as it processes BPF object file and prepares everything for /// uploading into the kernel. So [`OpenProgram::insns`] and [`Program::insns`] may return /// different sets of instructions. As an example, during BPF object load phase BPF program /// instructions will be CO-RE-relocated, BPF subprograms instructions will be appended, ldimm64 /// instructions will have FDs embedded, etc. So instructions returned before load and after it /// might be quite different. pub fn insns(&self) -> &[libbpf_sys::bpf_insn] { let count = self.insn_cnt(); let ptr = unsafe { libbpf_sys::bpf_program__insns(self.ptr.as_ptr()) }; unsafe { slice::from_raw_parts(ptr, count) } } } impl<'obj> OpenProgramMut<'obj> { /// Create a new [`OpenProgram`] from a ptr to a `libbpf_sys::bpf_program`. pub fn new_mut(prog: &'obj mut libbpf_sys::bpf_program) -> Self { Self { ptr: unsafe { NonNull::new_unchecked(prog as *mut _) }, _phantom: PhantomData, } } pub fn set_prog_type(&mut self, prog_type: ProgramType) { let rc = unsafe { libbpf_sys::bpf_program__set_type(self.ptr.as_ptr(), prog_type as u32) }; debug_assert!(util::parse_ret(rc).is_ok(), "{rc}"); } pub fn set_attach_type(&mut self, attach_type: ProgramAttachType) { let rc = unsafe { libbpf_sys::bpf_program__set_expected_attach_type(self.ptr.as_ptr(), attach_type as u32) }; debug_assert!(util::parse_ret(rc).is_ok(), "{rc}"); } pub fn set_ifindex(&mut self, idx: u32) { unsafe { libbpf_sys::bpf_program__set_ifindex(self.ptr.as_ptr(), idx) } } /// Set the log level for the bpf program. /// /// The log level is interpreted by bpf kernel code and interpretation may /// change with newer kernel versions. Refer to the kernel source code for /// details. /// /// In general, a value of `0` disables logging while values `> 0` enables /// it. pub fn set_log_level(&mut self, log_level: u32) { let rc = unsafe { libbpf_sys::bpf_program__set_log_level(self.ptr.as_ptr(), log_level) }; debug_assert!(util::parse_ret(rc).is_ok(), "{rc}"); } /// Set whether a bpf program should be automatically loaded by default /// when the bpf object is loaded. pub fn set_autoload(&mut self, autoload: bool) { let rc = unsafe { libbpf_sys::bpf_program__set_autoload(self.ptr.as_ptr(), autoload) }; debug_assert!(util::parse_ret(rc).is_ok(), "{rc}"); } pub fn set_attach_target( &mut self, attach_prog_fd: i32, attach_func_name: Option<String>, ) -> Result<()> { let ret = if let Some(name) = attach_func_name { // NB: we must hold onto a CString otherwise our pointer dangles let name_c = util::str_to_cstring(&name)?; unsafe { libbpf_sys::bpf_program__set_attach_target( self.ptr.as_ptr(), attach_prog_fd, name_c.as_ptr(), ) } } else { unsafe { libbpf_sys::bpf_program__set_attach_target( self.ptr.as_ptr(), attach_prog_fd, ptr::null(), ) } }; util::parse_ret(ret) } pub fn set_flags(&mut self, flags: u32) { let rc = unsafe { libbpf_sys::bpf_program__set_flags(self.ptr.as_ptr(), flags) }; debug_assert!(util::parse_ret(rc).is_ok(), "{rc}"); } } impl<'obj> Deref for OpenProgramMut<'obj> { type Target = OpenProgram<'obj>; fn deref(&self) -> &Self::Target { // SAFETY: `OpenProgramImpl` is `repr(transparent)` and so // in-memory representation of both types is the same. unsafe { transmute::<&OpenProgramMut<'obj>, &OpenProgram<'obj>>(self) } } } impl<T> AsRawLibbpf for OpenProgramImpl<'_, T> { type LibbpfType = libbpf_sys::bpf_program; /// Retrieve the underlying [`libbpf_sys::bpf_program`]. fn as_libbpf_object(&self) -> NonNull<Self::LibbpfType> { self.ptr } } /// Type of a [`Program`]. Maps to `enum bpf_prog_type` in kernel uapi. #[non_exhaustive] #[repr(u32)] #[derive(Copy, Clone, Debug)] // TODO: Document variants. #[allow(missing_docs)] pub enum ProgramType { Unspec = 0, SocketFilter, Kprobe, SchedCls, SchedAct, Tracepoint, Xdp, PerfEvent, CgroupSkb, CgroupSock, LwtIn, LwtOut, LwtXmit, SockOps, SkSkb, CgroupDevice, SkMsg, RawTracepoint, CgroupSockAddr, LwtSeg6local, LircMode2, SkReuseport, FlowDissector, CgroupSysctl, RawTracepointWritable, CgroupSockopt, Tracing, StructOps, Ext, Lsm, SkLookup, Syscall, /// See [`MapType::Unknown`][crate::MapType::Unknown] Unknown = u32::MAX, } impl ProgramType { /// Detects if host kernel supports this BPF program type /// /// Make sure the process has required set of CAP_* permissions (or runs as /// root) when performing feature checking. pub fn is_supported(&self) -> Result<bool> { let ret = unsafe { libbpf_sys::libbpf_probe_bpf_prog_type(*self as u32, ptr::null()) }; match ret { 0 => Ok(false), 1 => Ok(true), _ => Err(Error::from_raw_os_error(-ret)), } } /// Detects if host kernel supports the use of a given BPF helper from this BPF program type. /// * `helper_id` - BPF helper ID (enum bpf_func_id) to check support for /// /// Make sure the process has required set of CAP_* permissions (or runs as /// root) when performing feature checking. pub fn is_helper_supported(&self, helper_id: bpf_func_id) -> Result<bool> { let ret = unsafe { libbpf_sys::libbpf_probe_bpf_helper(*self as u32, helper_id, ptr::null()) }; match ret { 0 => Ok(false), 1 => Ok(true), _ => Err(Error::from_raw_os_error(-ret)), } } } impl From<u32> for ProgramType { fn from(value: u32) -> Self { use ProgramType::*; match value { x if x == Unspec as u32 => Unspec, x if x == SocketFilter as u32 => SocketFilter, x if x == Kprobe as u32 => Kprobe, x if x == SchedCls as u32 => SchedCls, x if x == SchedAct as u32 => SchedAct, x if x == Tracepoint as u32 => Tracepoint, x if x == Xdp as u32 => Xdp, x if x == PerfEvent as u32 => PerfEvent, x if x == CgroupSkb as u32 => CgroupSkb, x if x == CgroupSock as u32 => CgroupSock, x if x == LwtIn as u32 => LwtIn, x if x == LwtOut as u32 => LwtOut, x if x == LwtXmit as u32 => LwtXmit, x if x == SockOps as u32 => SockOps, x if x == SkSkb as u32 => SkSkb, x if x == CgroupDevice as u32 => CgroupDevice, x if x == SkMsg as u32 => SkMsg, x if x == RawTracepoint as u32 => RawTracepoint, x if x == CgroupSockAddr as u32 => CgroupSockAddr, x if x == LwtSeg6local as u32 => LwtSeg6local, x if x == LircMode2 as u32 => LircMode2, x if x == SkReuseport as u32 => SkReuseport, x if x == FlowDissector as u32 => FlowDissector, x if x == CgroupSysctl as u32 => CgroupSysctl, x if x == RawTracepointWritable as u32 => RawTracepointWritable, x if x == CgroupSockopt as u32 => CgroupSockopt, x if x == Tracing as u32 => Tracing, x if x == StructOps as u32 => StructOps, x if x == Ext as u32 => Ext, x if x == Lsm as u32 => Lsm, x if x == SkLookup as u32 => SkLookup, x if x == Syscall as u32 => Syscall, _ => Unknown, } } } /// Attach type of a [`Program`]. Maps to `enum bpf_attach_type` in kernel uapi. #[non_exhaustive] #[repr(u32)] #[derive(Clone, Debug)] // TODO: Document variants. #[allow(missing_docs)] pub enum ProgramAttachType { CgroupInetIngress, CgroupInetEgress, CgroupInetSockCreate, CgroupSockOps, SkSkbStreamParser, SkSkbStreamVerdict, CgroupDevice, SkMsgVerdict, CgroupInet4Bind, CgroupInet6Bind, CgroupInet4Connect, CgroupInet6Connect, CgroupInet4PostBind, CgroupInet6PostBind, CgroupUdp4Sendmsg, CgroupUdp6Sendmsg, LircMode2, FlowDissector, CgroupSysctl, CgroupUdp4Recvmsg, CgroupUdp6Recvmsg, CgroupGetsockopt, CgroupSetsockopt, TraceRawTp, TraceFentry, TraceFexit, ModifyReturn, LsmMac, TraceIter, CgroupInet4Getpeername, CgroupInet6Getpeername, CgroupInet4Getsockname, CgroupInet6Getsockname, XdpDevmap, CgroupInetSockRelease, XdpCpumap, SkLookup, Xdp, SkSkbVerdict, SkReuseportSelect, SkReuseportSelectOrMigrate, PerfEvent, /// See [`MapType::Unknown`][crate::MapType::Unknown] Unknown = u32::MAX, } impl From<u32> for ProgramAttachType { fn from(value: u32) -> Self { use ProgramAttachType::*; match value { x if x == CgroupInetIngress as u32 => CgroupInetIngress, x if x == CgroupInetEgress as u32 => CgroupInetEgress, x if x == CgroupInetSockCreate as u32 => CgroupInetSockCreate, x if x == CgroupSockOps as u32 => CgroupSockOps, x if x == SkSkbStreamParser as u32 => SkSkbStreamParser, x if x == SkSkbStreamVerdict as u32 => SkSkbStreamVerdict, x if x == CgroupDevice as u32 => CgroupDevice, x if x == SkMsgVerdict as u32 => SkMsgVerdict, x if x == CgroupInet4Bind as u32 => CgroupInet4Bind, x if x == CgroupInet6Bind as u32 => CgroupInet6Bind, x if x == CgroupInet4Connect as u32 => CgroupInet4Connect, x if x == CgroupInet6Connect as u32 => CgroupInet6Connect, x if x == CgroupInet4PostBind as u32 => CgroupInet4PostBind, x if x == CgroupInet6PostBind as u32 => CgroupInet6PostBind, x if x == CgroupUdp4Sendmsg as u32 => CgroupUdp4Sendmsg, x if x == CgroupUdp6Sendmsg as u32 => CgroupUdp6Sendmsg, x if x == LircMode2 as u32 => LircMode2, x if x == FlowDissector as u32 => FlowDissector, x if x == CgroupSysctl as u32 => CgroupSysctl, x if x == CgroupUdp4Recvmsg as u32 => CgroupUdp4Recvmsg, x if x == CgroupUdp6Recvmsg as u32 => CgroupUdp6Recvmsg, x if x == CgroupGetsockopt as u32 => CgroupGetsockopt, x if x == CgroupSetsockopt as u32 => CgroupSetsockopt, x if x == TraceRawTp as u32 => TraceRawTp, x if x == TraceFentry as u32 => TraceFentry, x if x == TraceFexit as u32 => TraceFexit, x if x == ModifyReturn as u32 => ModifyReturn, x if x == LsmMac as u32 => LsmMac, x if x == TraceIter as u32 => TraceIter, x if x == CgroupInet4Getpeername as u32 => CgroupInet4Getpeername, x if x == CgroupInet6Getpeername as u32 => CgroupInet6Getpeername, x if x == CgroupInet4Getsockname as u32 => CgroupInet4Getsockname, x if x == CgroupInet6Getsockname as u32 => CgroupInet6Getsockname, x if x == XdpDevmap as u32 => XdpDevmap, x if x == CgroupInetSockRelease as u32 => CgroupInetSockRelease, x if x == XdpCpumap as u32 => XdpCpumap, x if x == SkLookup as u32 => SkLookup, x if x == Xdp as u32 => Xdp, x if x == SkSkbVerdict as u32 => SkSkbVerdict, x if x == SkReuseportSelect as u32 => SkReuseportSelect, x if x == SkReuseportSelectOrMigrate as u32 => SkReuseportSelectOrMigrate, x if x == PerfEvent as u32 => PerfEvent, _ => Unknown, } } } /// The input a program accepts. /// /// This type is mostly used in conjunction with the [`Program::test_run`] /// facility. #[derive(Debug, Default)] pub struct Input<'dat> { /// The input context to provide. /// /// The input is mutable because the kernel may modify it. pub context_in: Option<&'dat mut [u8]>, /// The output context buffer provided to the program. pub context_out: Option<&'dat mut [u8]>, /// Additional data to provide to the program. pub data_in: Option<&'dat [u8]>, /// The output data buffer provided to the program. pub data_out: Option<&'dat mut [u8]>, /// The 'cpu' value passed to the kernel. pub cpu: u32, /// The 'flags' value passed to the kernel. pub flags: u32, /// The struct is non-exhaustive and open to extension. #[doc(hidden)] pub _non_exhaustive: (), } /// The output a program produces. /// /// This type is mostly used in conjunction with the [`Program::test_run`] /// facility. #[derive(Debug)] pub struct Output<'dat> { /// The value returned by the program. pub return_value: u32, /// The output context filled by the program/kernel. pub context: Option<&'dat mut [u8]>, /// Output data filled by the program. pub data: Option<&'dat mut [u8]>, /// The struct is non-exhaustive and open to extension. #[doc(hidden)] pub _non_exhaustive: (), } /// An immutable loaded BPF program. pub type Program<'obj> = ProgramImpl<'obj>; /// A mutable loaded BPF program. pub type ProgramMut<'obj> = ProgramImpl<'obj, Mut>; /// Represents a loaded [`Program`]. /// /// This struct is not safe to clone because the underlying libbpf resource cannot currently /// be protected from data races. /// /// If you attempt to attach a `Program` with the wrong attach method, the `attach_*` /// method will fail with the appropriate error. #[derive(Debug)] #[repr(transparent)] pub struct ProgramImpl<'obj, T = ()> { pub(crate) ptr: NonNull<libbpf_sys::bpf_program>, _phantom: PhantomData<&'obj T>, } impl<'obj> Program<'obj> { /// Create a [`Program`] from a [`libbpf_sys::bpf_program`] pub fn new(prog: &'obj libbpf_sys::bpf_program) -> Self { // SAFETY: We inferred the address from a reference, which is always // valid. Self { ptr: unsafe { NonNull::new_unchecked(prog as *const _ as *mut _) }, _phantom: PhantomData, } } /// Retrieve the name of this `Program`. pub fn name(&self) -> &OsStr { let name_ptr = unsafe { libbpf_sys::bpf_program__name(self.ptr.as_ptr()) }; let name_c_str = unsafe { CStr::from_ptr(name_ptr) }; // SAFETY: `bpf_program__name` always returns a non-NULL pointer. OsStr::from_bytes(name_c_str.to_bytes()) } /// Retrieve the name of the section this `Program` belongs to. pub fn section(&self) -> &OsStr { // SAFETY: The program is always valid. let p = unsafe { libbpf_sys::bpf_program__section_name(self.ptr.as_ptr()) }; // SAFETY: `bpf_program__section_name` will always return a non-NULL // pointer. let section_c_str = unsafe { CStr::from_ptr(p) }; let section = OsStr::from_bytes(section_c_str.to_bytes()); section } /// Retrieve the type of the program. pub fn prog_type(&self) -> ProgramType { ProgramType::from(unsafe { libbpf_sys::bpf_program__type(self.ptr.as_ptr()) }) } /// Returns program fd by id pub fn get_fd_by_id(id: u32) -> Result<OwnedFd> { let ret = unsafe { libbpf_sys::bpf_prog_get_fd_by_id(id) }; let fd = util::parse_ret_i32(ret)?; // SAFETY // A file descriptor coming from the bpf_prog_get_fd_by_id function is always suitable for // ownership and can be cleaned up with close. Ok(unsafe { OwnedFd::from_raw_fd(fd) }) } /// Returns program id by fd pub fn get_id_by_fd(fd: BorrowedFd<'_>) -> Result<u32> { let mut prog_info = libbpf_sys::bpf_prog_info::default(); let prog_info_ptr: *mut libbpf_sys::bpf_prog_info = &mut prog_info; let mut len = size_of::<libbpf_sys::bpf_prog_info>() as u32; let ret = unsafe { libbpf_sys::bpf_obj_get_info_by_fd( fd.as_raw_fd(), prog_info_ptr as *mut c_void, &mut len, ) }; util::parse_ret(ret)?; Ok(prog_info.id) } /// Returns flags that have been set for the program. pub fn flags(&self) -> u32 { unsafe { libbpf_sys::bpf_program__flags(self.ptr.as_ptr()) } } /// Retrieve the attach type of the program. pub fn attach_type(&self) -> ProgramAttachType { ProgramAttachType::from(unsafe { libbpf_sys::bpf_program__expected_attach_type(self.ptr.as_ptr()) }) } /// Return `true` if the bpf program is set to autoload, `false` otherwise. pub fn autoload(&self) -> bool { unsafe { libbpf_sys::bpf_program__autoload(self.ptr.as_ptr()) } } /// Return the bpf program's log level. pub fn log_level(&self) -> u32 { unsafe { libbpf_sys::bpf_program__log_level(self.ptr.as_ptr()) } } /// Returns the number of instructions that form the program. /// /// Please see note in [`OpenProgram::insn_cnt`]. pub fn insn_cnt(&self) -> usize { unsafe { libbpf_sys::bpf_program__insn_cnt(self.ptr.as_ptr()) as usize } } /// Gives read-only access to BPF program's underlying BPF instructions. /// /// Please see note in [`OpenProgram::insns`]. pub fn insns(&self) -> &[libbpf_sys::bpf_insn] { let count = self.insn_cnt(); let ptr = unsafe { libbpf_sys::bpf_program__insns(self.ptr.as_ptr()) }; unsafe { slice::from_raw_parts(ptr, count) } } } impl<'obj> ProgramMut<'obj> { /// Create a [`Program`] from a [`libbpf_sys::bpf_program`] pub fn new_mut(prog: &'obj mut libbpf_sys::bpf_program) -> Self { Self { ptr: unsafe { NonNull::new_unchecked(prog as *mut _) }, _phantom: PhantomData, } } /// [Pin](https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html#bpffs) /// this program to bpffs. pub fn pin<P: AsRef<Path>>(&mut self, path: P) -> Result<()> { let path_c = util::path_to_cstring(path)?; let path_ptr = path_c.as_ptr(); let ret = unsafe { libbpf_sys::bpf_program__pin(self.ptr.as_ptr(), path_ptr) }; util::parse_ret(ret) } /// [Unpin](https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html#bpffs) /// this program from bpffs pub fn unpin<P: AsRef<Path>>(&mut self, path: P) -> Result<()> { let path_c = util::path_to_cstring(path)?; let path_ptr = path_c.as_ptr(); let ret = unsafe { libbpf_sys::bpf_program__unpin(self.ptr.as_ptr(), path_ptr) }; util::parse_ret(ret) } /// Auto-attach based on prog section pub fn attach(&mut self) -> Result<Link> { let ptr = unsafe { libbpf_sys::bpf_program__attach(self.ptr.as_ptr()) }; let ptr = validate_bpf_ret(ptr).context("failed to attach BPF program")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach this program to a /// [cgroup](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html). pub fn attach_cgroup(&mut self, cgroup_fd: i32) -> Result<Link> { let ptr = unsafe { libbpf_sys::bpf_program__attach_cgroup(self.ptr.as_ptr(), cgroup_fd) }; let ptr = validate_bpf_ret(ptr).context("failed to attach cgroup")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach this program to a [perf event](https://linux.die.net/man/2/perf_event_open). pub fn attach_perf_event(&mut self, pfd: i32) -> Result<Link> { let ptr = unsafe { libbpf_sys::bpf_program__attach_perf_event(self.ptr.as_ptr(), pfd) }; let ptr = validate_bpf_ret(ptr).context("failed to attach perf event")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach this program to a [userspace /// probe](https://www.kernel.org/doc/html/latest/trace/uprobetracer.html). pub fn attach_uprobe<T: AsRef<Path>>( &mut self, retprobe: bool, pid: i32, binary_path: T, func_offset: usize, ) -> Result<Link> { let path = util::path_to_cstring(binary_path)?; let path_ptr = path.as_ptr(); let ptr = unsafe { libbpf_sys::bpf_program__attach_uprobe( self.ptr.as_ptr(), retprobe, pid, path_ptr, func_offset as libbpf_sys::size_t, ) }; let ptr = validate_bpf_ret(ptr).context("failed to attach uprobe")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach this program to a [userspace /// probe](https://www.kernel.org/doc/html/latest/trace/uprobetracer.html), /// providing additional options. pub fn attach_uprobe_with_opts( &mut self, pid: i32, binary_path: impl AsRef<Path>, func_offset: usize, opts: UprobeOpts, ) -> Result<Link> { let path = util::path_to_cstring(binary_path)?; let path_ptr = path.as_ptr(); let UprobeOpts { ref_ctr_offset, cookie, retprobe, func_name, _non_exhaustive, } = opts; let func_name = util::str_to_cstring(&func_name)?; let opts = libbpf_sys::bpf_uprobe_opts { sz: size_of::<libbpf_sys::bpf_uprobe_opts>() as _, ref_ctr_offset: ref_ctr_offset as libbpf_sys::size_t, bpf_cookie: cookie, retprobe, func_name: func_name.as_ptr(), ..Default::default() }; let ptr = unsafe { libbpf_sys::bpf_program__attach_uprobe_opts( self.ptr.as_ptr(), pid, path_ptr, func_offset as libbpf_sys::size_t, &opts as *const _, ) }; let ptr = validate_bpf_ret(ptr).context("failed to attach uprobe")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach this program to a [kernel /// probe](https://www.kernel.org/doc/html/latest/trace/kprobetrace.html). pub fn attach_kprobe<T: AsRef<str>>(&mut self, retprobe: bool, func_name: T) -> Result<Link> { let func_name = util::str_to_cstring(func_name.as_ref())?; let func_name_ptr = func_name.as_ptr(); let ptr = unsafe { libbpf_sys::bpf_program__attach_kprobe(self.ptr.as_ptr(), retprobe, func_name_ptr) }; let ptr = validate_bpf_ret(ptr).context("failed to attach kprobe")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach this program to the specified syscall pub fn attach_ksyscall<T: AsRef<str>>( &mut self, retprobe: bool, syscall_name: T, ) -> Result<Link> { let opts = libbpf_sys::bpf_ksyscall_opts { sz: size_of::<libbpf_sys::bpf_ksyscall_opts>() as _, retprobe, ..Default::default() }; let syscall_name = util::str_to_cstring(syscall_name.as_ref())?; let syscall_name_ptr = syscall_name.as_ptr(); let ptr = unsafe { libbpf_sys::bpf_program__attach_ksyscall(self.ptr.as_ptr(), syscall_name_ptr, &opts) }; let ptr = validate_bpf_ret(ptr).context("failed to attach ksyscall")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } fn attach_tracepoint_impl( &mut self, tp_category: &str, tp_name: &str, tp_opts: Option<TracepointOpts>, ) -> Result<Link> { let tp_category = util::str_to_cstring(tp_category)?; let tp_category_ptr = tp_category.as_ptr(); let tp_name = util::str_to_cstring(tp_name)?; let tp_name_ptr = tp_name.as_ptr(); let ptr = if let Some(tp_opts) = tp_opts { let tp_opts = libbpf_sys::bpf_tracepoint_opts::from(tp_opts); unsafe { libbpf_sys::bpf_program__attach_tracepoint_opts( self.ptr.as_ptr(), tp_category_ptr, tp_name_ptr, &tp_opts as *const _, ) } } else { unsafe { libbpf_sys::bpf_program__attach_tracepoint( self.ptr.as_ptr(), tp_category_ptr, tp_name_ptr, ) } }; let ptr = validate_bpf_ret(ptr).context("failed to attach tracepoint")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach this program to a [kernel /// tracepoint](https://www.kernel.org/doc/html/latest/trace/tracepoints.html). pub fn attach_tracepoint( &mut self, tp_category: impl AsRef<str>, tp_name: impl AsRef<str>, ) -> Result<Link> { self.attach_tracepoint_impl(tp_category.as_ref(), tp_name.as_ref(), None) } /// Attach this program to a [kernel /// tracepoint](https://www.kernel.org/doc/html/latest/trace/tracepoints.html), /// providing additional options. pub fn attach_tracepoint_with_opts( &mut self, tp_category: impl AsRef<str>, tp_name: impl AsRef<str>, tp_opts: TracepointOpts, ) -> Result<Link> { self.attach_tracepoint_impl(tp_category.as_ref(), tp_name.as_ref(), Some(tp_opts)) } /// Attach this program to a [raw kernel /// tracepoint](https://lwn.net/Articles/748352/). pub fn attach_raw_tracepoint<T: AsRef<str>>(&mut self, tp_name: T) -> Result<Link> { let tp_name = util::str_to_cstring(tp_name.as_ref())?; let tp_name_ptr = tp_name.as_ptr(); let ptr = unsafe { libbpf_sys::bpf_program__attach_raw_tracepoint(self.ptr.as_ptr(), tp_name_ptr) }; let ptr = validate_bpf_ret(ptr).context("failed to attach raw tracepoint")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach to an [LSM](https://en.wikipedia.org/wiki/Linux_Security_Modules) hook pub fn attach_lsm(&mut self) -> Result<Link> { let ptr = unsafe { libbpf_sys::bpf_program__attach_lsm(self.ptr.as_ptr()) }; let ptr = validate_bpf_ret(ptr).context("failed to attach LSM")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach to a [fentry/fexit kernel probe](https://lwn.net/Articles/801479/) pub fn attach_trace(&mut self) -> Result<Link> { let ptr = unsafe { libbpf_sys::bpf_program__attach_trace(self.ptr.as_ptr()) }; let ptr = validate_bpf_ret(ptr).context("failed to attach fentry/fexit kernel probe")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach a verdict/parser to a [sockmap/sockhash](https://lwn.net/Articles/731133/) pub fn attach_sockmap(&self, map_fd: i32) -> Result<()> { let err = unsafe { libbpf_sys::bpf_prog_attach( self.as_fd().as_raw_fd(), map_fd, self.attach_type() as u32, 0, ) }; util::parse_ret(err) } /// Attach this program to [XDP](https://lwn.net/Articles/825998/) pub fn attach_xdp(&mut self, ifindex: i32) -> Result<Link> { let ptr = unsafe { libbpf_sys::bpf_program__attach_xdp(self.ptr.as_ptr(), ifindex) }; let ptr = validate_bpf_ret(ptr).context("failed to attach XDP program")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach this program to [netns-based programs](https://lwn.net/Articles/819618/) pub fn attach_netns(&mut self, netns_fd: i32) -> Result<Link> { let ptr = unsafe { libbpf_sys::bpf_program__attach_netns(self.ptr.as_ptr(), netns_fd) }; let ptr = validate_bpf_ret(ptr).context("failed to attach network namespace program")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } fn attach_usdt_impl( &mut self, pid: i32, binary_path: &Path, usdt_provider: &str, usdt_name: &str, usdt_opts: Option<UsdtOpts>, ) -> Result<Link> { let path = util::path_to_cstring(binary_path)?; let path_ptr = path.as_ptr(); let usdt_provider = util::str_to_cstring(usdt_provider)?; let usdt_provider_ptr = usdt_provider.as_ptr(); let usdt_name = util::str_to_cstring(usdt_name)?; let usdt_name_ptr = usdt_name.as_ptr(); let usdt_opts = usdt_opts.map(libbpf_sys::bpf_usdt_opts::from); let usdt_opts_ptr = usdt_opts .as_ref() .map(|opts| opts as *const _) .unwrap_or_else(ptr::null); let ptr = unsafe { libbpf_sys::bpf_program__attach_usdt( self.ptr.as_ptr(), pid, path_ptr, usdt_provider_ptr, usdt_name_ptr, usdt_opts_ptr, ) }; let ptr = validate_bpf_ret(ptr).context("failed to attach USDT")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Attach this program to a [USDT](https://lwn.net/Articles/753601/) probe /// point. The entry point of the program must be defined with /// `SEC("usdt")`. pub fn attach_usdt( &mut self, pid: i32, binary_path: impl AsRef<Path>, usdt_provider: impl AsRef<str>, usdt_name: impl AsRef<str>, ) -> Result<Link> { self.attach_usdt_impl( pid, binary_path.as_ref(), usdt_provider.as_ref(), usdt_name.as_ref(), None, ) } /// Attach this program to a [USDT](https://lwn.net/Articles/753601/) probe /// point, providing additional options. The entry point of the program must /// be defined with `SEC("usdt")`. pub fn attach_usdt_with_opts( &mut self, pid: i32, binary_path: impl AsRef<Path>, usdt_provider: impl AsRef<str>, usdt_name: impl AsRef<str>, usdt_opts: UsdtOpts, ) -> Result<Link> { self.attach_usdt_impl( pid, binary_path.as_ref(), usdt_provider.as_ref(), usdt_name.as_ref(), Some(usdt_opts), ) } /// Attach this program to a /// [BPF Iterator](https://www.kernel.org/doc/html/latest/bpf/bpf_iterators.html). /// The entry point of the program must be defined with `SEC("iter")` or `SEC("iter.s")`. pub fn attach_iter(&mut self, map_fd: BorrowedFd<'_>) -> Result<Link> { let mut linkinfo = libbpf_sys::bpf_iter_link_info::default(); linkinfo.map.map_fd = map_fd.as_raw_fd() as _; let attach_opt = libbpf_sys::bpf_iter_attach_opts { link_info: &mut linkinfo as *mut libbpf_sys::bpf_iter_link_info, link_info_len: size_of::<libbpf_sys::bpf_iter_link_info>() as _, sz: size_of::<libbpf_sys::bpf_iter_attach_opts>() as _, ..Default::default() }; let ptr = unsafe { libbpf_sys::bpf_program__attach_iter( self.ptr.as_ptr(), &attach_opt as *const libbpf_sys::bpf_iter_attach_opts, ) }; let ptr = validate_bpf_ret(ptr).context("failed to attach iterator")?; // SAFETY: the pointer came from libbpf and has been checked for errors. let link = unsafe { Link::new(ptr) }; Ok(link) } /// Test run the program with the given input data. /// /// This function uses the /// [BPF_PROG_RUN](https://www.kernel.org/doc/html/latest/bpf/bpf_prog_run.html) /// facility. pub fn test_run<'dat>(&mut self, input: Input<'dat>) -> Result<Output<'dat>> { unsafe fn slice_from_array<'t, T>(items: *mut T, num_items: usize) -> Option<&'t mut [T]> { if items.is_null() { None } else { Some(unsafe { slice::from_raw_parts_mut(items, num_items) }) } } let Input { context_in, mut context_out, data_in, mut data_out, cpu, flags, _non_exhaustive: (), } = input; let mut opts = unsafe { mem::zeroed::<libbpf_sys::bpf_test_run_opts>() }; opts.sz = size_of_val(&opts) as _; opts.ctx_in = context_in .as_ref() .map(|data| data.as_ptr().cast()) .unwrap_or_else(ptr::null); opts.ctx_size_in = context_in.map(|data| data.len() as _).unwrap_or(0); opts.ctx_out = context_out .as_mut() .map(|data| data.as_mut_ptr().cast()) .unwrap_or_else(ptr::null_mut); opts.ctx_size_out = context_out.map(|data| data.len() as _).unwrap_or(0); opts.data_in = data_in .map(|data| data.as_ptr().cast()) .unwrap_or_else(ptr::null); opts.data_size_in = data_in.map(|data| data.len() as _).unwrap_or(0); opts.data_out = data_out .as_mut() .map(|data| data.as_mut_ptr().cast()) .unwrap_or_else(ptr::null_mut); opts.data_size_out = data_out.map(|data| data.len() as _).unwrap_or(0); opts.cpu = cpu; opts.flags = flags; let rc = unsafe { libbpf_sys::bpf_prog_test_run_opts(self.as_fd().as_raw_fd(), &mut opts) }; let () = util::parse_ret(rc)?; let output = Output { return_value: opts.retval, context: unsafe { slice_from_array(opts.ctx_out.cast(), opts.ctx_size_out as _) }, data: unsafe { slice_from_array(opts.data_out.cast(), opts.data_size_out as _) }, _non_exhaustive: (), }; Ok(output) } } impl<'obj> Deref for ProgramMut<'obj> { type Target = Program<'obj>; fn deref(&self) -> &Self::Target { // SAFETY: `ProgramImpl` is `repr(transparent)` and so in-memory // representation of both types is the same. unsafe { transmute::<&ProgramMut<'obj>, &Program<'obj>>(self) } } } impl<T> AsFd for ProgramImpl<'_, T> { fn as_fd(&self) -> BorrowedFd<'_> { let fd = unsafe { libbpf_sys::bpf_program__fd(self.ptr.as_ptr()) }; unsafe { BorrowedFd::borrow_raw(fd) } } } impl<T> AsRawLibbpf for ProgramImpl<'_, T> { type LibbpfType = libbpf_sys::bpf_program; /// Retrieve the underlying [`libbpf_sys::bpf_program`]. fn as_libbpf_object(&self) -> NonNull<Self::LibbpfType> { self.ptr } } #[cfg(test)] mod tests { use super::*; use std::mem::discriminant; #[test] fn program_type() { use ProgramType::*; for t in [ Unspec, SocketFilter, Kprobe, SchedCls, SchedAct, Tracepoint, Xdp, PerfEvent, CgroupSkb, CgroupSock, LwtIn, LwtOut, LwtXmit, SockOps, SkSkb, CgroupDevice, SkMsg, RawTracepoint, CgroupSockAddr, LwtSeg6local, LircMode2, SkReuseport, FlowDissector, CgroupSysctl, RawTracepointWritable, CgroupSockopt, Tracing, StructOps, Ext, Lsm, SkLookup, Syscall, Unknown, ] { // check if discriminants match after a roundtrip conversion assert_eq!(discriminant(&t), discriminant(&ProgramType::from(t as u32))); } } #[test] fn program_attach_type() { use ProgramAttachType::*; for t in [ CgroupInetIngress, CgroupInetEgress, CgroupInetSockCreate, CgroupSockOps, SkSkbStreamParser, SkSkbStreamVerdict, CgroupDevice, SkMsgVerdict, CgroupInet4Bind, CgroupInet6Bind, CgroupInet4Connect, CgroupInet6Connect, CgroupInet4PostBind, CgroupInet6PostBind, CgroupUdp4Sendmsg, CgroupUdp6Sendmsg, LircMode2, FlowDissector, CgroupSysctl, CgroupUdp4Recvmsg, CgroupUdp6Recvmsg, CgroupGetsockopt, CgroupSetsockopt, TraceRawTp, TraceFentry, TraceFexit, ModifyReturn, LsmMac, TraceIter, CgroupInet4Getpeername, CgroupInet6Getpeername, CgroupInet4Getsockname, CgroupInet6Getsockname, XdpDevmap, CgroupInetSockRelease, XdpCpumap, SkLookup, Xdp, SkSkbVerdict, SkReuseportSelect, SkReuseportSelectOrMigrate, PerfEvent, Unknown, ] { // check if discriminants match after a roundtrip conversion assert_eq!( discriminant(&t), discriminant(&ProgramAttachType::from(t as u32)) ); } } }