diff --git a/aya/src/programs/cgroup_device.rs b/aya/src/programs/cgroup_device.rs index 73d465ca..87c5d4a2 100644 --- a/aya/src/programs/cgroup_device.rs +++ b/aya/src/programs/cgroup_device.rs @@ -8,7 +8,7 @@ use crate::{ bpf_prog_get_fd_by_id, define_link_wrapper, load_program, query, CgroupAttachMode, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, ProgramFd, }, - sys::{bpf_link_create, LinkTarget, SyscallError}, + sys::{bpf_link_create, LinkTarget, ProgQueryTarget, SyscallError}, util::KernelVersion, }; @@ -120,7 +120,12 @@ impl CgroupDevice { /// Queries the cgroup for attached programs. pub fn query(target_fd: T) -> Result, ProgramError> { let target_fd = target_fd.as_fd(); - let prog_ids = query(target_fd, BPF_CGROUP_DEVICE, 0, &mut None)?; + let (_, prog_ids) = query( + ProgQueryTarget::Fd(target_fd), + BPF_CGROUP_DEVICE, + 0, + &mut None, + )?; prog_ids .into_iter() diff --git a/aya/src/programs/lirc_mode2.rs b/aya/src/programs/lirc_mode2.rs index cc035d1c..fecd261d 100644 --- a/aya/src/programs/lirc_mode2.rs +++ b/aya/src/programs/lirc_mode2.rs @@ -7,7 +7,7 @@ use crate::{ load_program, query, CgroupAttachMode, Link, ProgramData, ProgramError, ProgramFd, ProgramInfo, }, - sys::{bpf_prog_attach, bpf_prog_detach, bpf_prog_get_fd_by_id}, + sys::{bpf_prog_attach, bpf_prog_detach, bpf_prog_get_fd_by_id, ProgQueryTarget}, }; /// A program used to decode IR into key events for a lirc device. @@ -100,7 +100,7 @@ impl LircMode2 { /// Queries the lirc device for attached programs. pub fn query(target_fd: T) -> Result, ProgramError> { let target_fd = target_fd.as_fd(); - let prog_ids = query(target_fd, BPF_LIRC_MODE2, 0, &mut None)?; + let (_, prog_ids) = query(ProgQueryTarget::Fd(target_fd), BPF_LIRC_MODE2, 0, &mut None)?; prog_ids .into_iter() diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 51565995..d2bc166a 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -73,7 +73,7 @@ use std::{ ffi::CString, io, num::TryFromIntError, - os::fd::{AsFd, AsRawFd, BorrowedFd}, + os::fd::{AsFd, BorrowedFd}, path::{Path, PathBuf}, sync::Arc, }; @@ -122,7 +122,7 @@ use crate::{ sys::{ bpf_btf_get_fd_by_id, bpf_get_object, bpf_link_get_fd_by_id, bpf_link_get_info_by_fd, bpf_load_program, bpf_pin_object, bpf_prog_get_fd_by_id, bpf_prog_query, iter_link_ids, - retry_with_verifier_logs, EbpfLoadProgramAttrs, SyscallError, + retry_with_verifier_logs, EbpfLoadProgramAttrs, ProgQueryTarget, SyscallError, }, util::KernelVersion, VerifierLogLevel, @@ -688,28 +688,30 @@ fn load_program( } pub(crate) fn query( - target_fd: BorrowedFd<'_>, + target: ProgQueryTarget<'_>, attach_type: bpf_attach_type, query_flags: u32, attach_flags: &mut Option, -) -> Result, ProgramError> { +) -> Result<(u64, Vec), ProgramError> { let mut prog_ids = vec![0u32; 64]; let mut prog_cnt = prog_ids.len() as u32; + let mut revision = 0; let mut retries = 0; loop { match bpf_prog_query( - target_fd.as_fd().as_raw_fd(), + &target, attach_type, query_flags, attach_flags.as_mut(), &mut prog_ids, &mut prog_cnt, + &mut revision, ) { Ok(_) => { prog_ids.resize(prog_cnt as usize, 0); - return Ok(prog_ids); + return Ok((revision, prog_ids)); } Err((_, io_error)) => { if retries == 0 && io_error.raw_os_error() == Some(ENOSPC) { diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs index 794a72dc..de697ab7 100644 --- a/aya/src/programs/tc.rs +++ b/aya/src/programs/tc.rs @@ -8,7 +8,7 @@ use std::{ use thiserror::Error; -use super::FdLink; +use super::{FdLink, ProgramInfo}; use crate::{ generated::{ bpf_attach_type::{self, BPF_TCX_EGRESS, BPF_TCX_INGRESS}, @@ -17,12 +17,13 @@ use crate::{ TC_H_CLSACT, TC_H_MIN_EGRESS, TC_H_MIN_INGRESS, }, programs::{ - define_link_wrapper, load_program, Link, LinkError, LinkOrder, ProgramData, ProgramError, + define_link_wrapper, load_program, query, Link, LinkError, LinkOrder, ProgramData, + ProgramError, }, sys::{ - bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, netlink_find_filter_with_name, - netlink_qdisc_add_clsact, netlink_qdisc_attach, netlink_qdisc_detach, LinkTarget, - SyscallError, + bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, bpf_prog_get_fd_by_id, + netlink_find_filter_with_name, netlink_qdisc_add_clsact, netlink_qdisc_attach, + netlink_qdisc_detach, LinkTarget, ProgQueryTarget, SyscallError, }, util::{ifindex_from_ifname, tc_handler_make, KernelVersion}, VerifierLogLevel, @@ -340,6 +341,46 @@ impl SchedClassifier { let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data }) } + + /// Queries a given interface for attached TCX programs. + /// + /// # Example + /// + /// ```no_run + /// # use aya::programs::tc::{TcAttachType, SchedClassifier}; + /// # #[derive(Debug, thiserror::Error)] + /// # enum Error { + /// # #[error(transparent)] + /// # Program(#[from] aya::programs::ProgramError), + /// # } + /// let (revision, programs) = SchedClassifier::query_tcx("eth0", TcAttachType::Ingress)?; + /// # Ok::<(), Error>(()) + /// ``` + pub fn query_tcx( + interface: &str, + attach_type: TcAttachType, + ) -> Result<(u64, Vec), ProgramError> { + let if_index = ifindex_from_ifname(interface) + .map_err(|io_error| TcError::NetlinkError { io_error })?; + + let (revision, prog_ids) = query( + ProgQueryTarget::IfIndex(if_index), + attach_type.tcx_attach_type()?, + 0, + &mut None, + )?; + + let prog_infos = prog_ids + .into_iter() + .map(|prog_id| { + let prog_fd = bpf_prog_get_fd_by_id(prog_id)?; + let prog_info = ProgramInfo::new_from_fd(prog_fd.as_fd())?; + Ok::(prog_info) + }) + .collect::>()?; + + Ok((revision, prog_infos)) + } } #[derive(Debug, Hash, Eq, PartialEq)] diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 6fb99597..a405b3be 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -498,25 +498,39 @@ pub(crate) fn bpf_prog_detach( Ok(()) } +#[derive(Debug)] +pub(crate) enum ProgQueryTarget<'a> { + Fd(BorrowedFd<'a>), + IfIndex(u32), +} + pub(crate) fn bpf_prog_query( - target_fd: RawFd, + target: &ProgQueryTarget<'_>, attach_type: bpf_attach_type, query_flags: u32, attach_flags: Option<&mut u32>, prog_ids: &mut [u32], prog_cnt: &mut u32, + revision: &mut u64, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; - attr.query.__bindgen_anon_1.target_fd = target_fd as u32; + match target { + ProgQueryTarget::Fd(fd) => { + attr.query.__bindgen_anon_1.target_fd = fd.as_raw_fd() as u32; + } + ProgQueryTarget::IfIndex(ifindex) => { + attr.query.__bindgen_anon_1.target_ifindex = *ifindex; + } + } attr.query.attach_type = attach_type as u32; attr.query.query_flags = query_flags; attr.query.__bindgen_anon_2.prog_cnt = prog_ids.len() as u32; attr.query.prog_ids = prog_ids.as_mut_ptr() as u64; - let ret = sys_bpf(bpf_cmd::BPF_PROG_QUERY, &mut attr); *prog_cnt = unsafe { attr.query.__bindgen_anon_2.prog_cnt }; + *revision = unsafe { attr.query.revision }; if let Some(attach_flags) = attach_flags { *attach_flags = unsafe { attr.query.attach_flags }; diff --git a/macro.patch b/macro.patch deleted file mode 100644 index e69de29b..00000000 diff --git a/test/integration-test/src/tests/tcx.rs b/test/integration-test/src/tests/tcx.rs index 2742a6d2..f334a024 100644 --- a/test/integration-test/src/tests/tcx.rs +++ b/test/integration-test/src/tests/tcx.rs @@ -1,12 +1,7 @@ -use std::collections::HashMap; - use aya::{ - programs::{ - tc::{SchedClassifierLink, TcAttachOptions}, - LinkOrder, ProgramId, SchedClassifier, TcAttachType, - }, + programs::{tc::TcAttachOptions, LinkOrder, ProgramId, SchedClassifier, TcAttachType}, util::KernelVersion, - Ebpf, EbpfLoader, + Ebpf, }; use test_log::test; @@ -25,110 +20,97 @@ async fn tcx() { // We need a dedicated `Ebpf` instance for each program that we load // since TCX does not allow the same program ID to be attached multiple // times to the same interface/direction. - let mut attached_programs: HashMap<&str, (Ebpf, SchedClassifierLink)> = HashMap::new(); + // + // Variables declared within this macro are within a closure scope to avoid + // variable name conflicts. + // + // Yields a tuple of the `Ebpf` which must remain in scope for the duration + // of the test, and the link ID of the attached program. macro_rules! attach_program_with_linkorder { - ($name:literal,$link_order:expr) => {{ - let mut loader = EbpfLoader::new().load(crate::TCX).unwrap(); + ($link_order:expr) => {{ + let mut ebpf = Ebpf::load(crate::TCX).unwrap(); let program: &mut SchedClassifier = - loader.program_mut("tcx_next").unwrap().try_into().unwrap(); + ebpf.program_mut("tcx_next").unwrap().try_into().unwrap(); program.load().unwrap(); - let options = TcAttachOptions::TcxOrder($link_order); let link_id = program - .attach_with_options("lo", TcAttachType::Ingress, options) + .attach_with_options( + "lo", + TcAttachType::Ingress, + TcAttachOptions::TcxOrder($link_order), + ) .unwrap(); - let link = program.take_link(link_id).unwrap(); - attached_programs.insert($name, (loader, link)); + (ebpf, link_id) }}; } - // TODO: Assert in position 4 at the end of the test. - attach_program_with_linkorder!("default", LinkOrder::default()); - // TODO: Assert in position 1 at the end of the test. - attach_program_with_linkorder!("first", LinkOrder::first()); - // TODO: Assert in position 7 at the end of the test. - attach_program_with_linkorder!("last", LinkOrder::last()); - // TODO: Assert in position 6 at the end of the test. - attach_program_with_linkorder!( - "before_last", - LinkOrder::before_link(&attached_programs.get("last").unwrap().1).unwrap() - ); - // TODO: Assert in position 8 at the end of the test. - attach_program_with_linkorder!( - "after_last", - LinkOrder::after_link(&attached_programs.get("last").unwrap().1).unwrap() - ); - // TODO: Assert in position 3 at the end of the test. - attach_program_with_linkorder!( - "before_default", - LinkOrder::before_program( - TryInto::<&SchedClassifier>::try_into( - attached_programs - .get("default") - .unwrap() - .0 - .program("tcx_next") - .unwrap(), - ) + let (default, _) = attach_program_with_linkorder!(LinkOrder::default()); + let (first, _) = attach_program_with_linkorder!(LinkOrder::first()); + let (mut last, last_link_id) = attach_program_with_linkorder!(LinkOrder::last()); + + let default_prog: &SchedClassifier = default.program("tcx_next").unwrap().try_into().unwrap(); + let first_prog: &SchedClassifier = first.program("tcx_next").unwrap().try_into().unwrap(); + let last_prog: &mut SchedClassifier = last.program_mut("tcx_next").unwrap().try_into().unwrap(); + + let last_link = last_prog.take_link(last_link_id).unwrap(); + + let (before_last, _) = + attach_program_with_linkorder!(LinkOrder::before_link(&last_link).unwrap()); + let (after_last, _) = + attach_program_with_linkorder!(LinkOrder::after_link(&last_link).unwrap()); + + let (before_default, _) = + attach_program_with_linkorder!(LinkOrder::before_program(default_prog).unwrap()); + let (after_default, _) = + attach_program_with_linkorder!(LinkOrder::after_program(default_prog).unwrap()); + + let (before_first, _) = attach_program_with_linkorder!(LinkOrder::before_program_id(unsafe { + ProgramId::new(first_prog.info().unwrap().id()) + })); + let (after_first, _) = attach_program_with_linkorder!(LinkOrder::after_program_id(unsafe { + ProgramId::new(first_prog.info().unwrap().id()) + })); + + let expected_order = [ + before_first + .program("tcx_next") .unwrap() - ) - .unwrap() - ); - // TODO: Assert in position 5 at the end of the test. - attach_program_with_linkorder!( - "after_default", - LinkOrder::after_program( - TryInto::<&SchedClassifier>::try_into( - attached_programs - .get("default") - .unwrap() - .0 - .program("tcx_next") - .unwrap(), - ) + .info() .unwrap() - ) - .unwrap() - ); - // TODO: Assert in position 0 at the end of the test. - attach_program_with_linkorder!( - "before_first", - LinkOrder::before_program_id(unsafe { - ProgramId::new( - TryInto::<&SchedClassifier>::try_into( - attached_programs - .get("first") - .unwrap() - .0 - .program("tcx_next") - .unwrap(), - ) - .unwrap() - .info() - .unwrap() - .id(), - ) - }) - ); - // TODO: Assert in position 2 at the end of the test. - attach_program_with_linkorder!( - "after_first", - LinkOrder::after_program_id(unsafe { - ProgramId::new( - TryInto::<&SchedClassifier>::try_into( - attached_programs - .get("first") - .unwrap() - .0 - .program("tcx_next") - .unwrap(), - ) - .unwrap() - .info() - .unwrap() - .id(), - ) - }) + .id(), + first_prog.info().unwrap().id(), + after_first + .program("tcx_next") + .unwrap() + .info() + .unwrap() + .id(), + before_default + .program("tcx_next") + .unwrap() + .info() + .unwrap() + .id(), + default_prog.info().unwrap().id(), + after_default + .program("tcx_next") + .unwrap() + .info() + .unwrap() + .id(), + before_last + .program("tcx_next") + .unwrap() + .info() + .unwrap() + .id(), + last_prog.info().unwrap().id(), + after_last.program("tcx_next").unwrap().info().unwrap().id(), + ]; + + let (revision, got_order) = SchedClassifier::query_tcx("lo", TcAttachType::Ingress).unwrap(); + assert_eq!(revision, (expected_order.len() + 1) as u64); + assert_eq!( + got_order.iter().map(|p| p.id()).collect::>(), + expected_order ); - // TODO: Add code here to automatically verify the order after the API based - // on the BPF_PROG_QUERY syscall is implemented. }