diff --git a/aya/src/programs/cgroup_device.rs b/aya/src/programs/cgroup_device.rs index 73d465ca..9f31172f 100644 --- a/aya/src/programs/cgroup_device.rs +++ b/aya/src/programs/cgroup_device.rs @@ -120,7 +120,7 @@ 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(Some(target_fd), None, 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..f4e71352 100644 --- a/aya/src/programs/lirc_mode2.rs +++ b/aya/src/programs/lirc_mode2.rs @@ -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(Some(target_fd), None, 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..dbded8f3 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -80,7 +80,7 @@ use std::{ use info::impl_info; pub use info::{loaded_programs, ProgramInfo, ProgramType}; -use libc::ENOSPC; +use libc::{if_nametoindex, ENOSPC}; use tc::SchedClassifierLink; use thiserror::Error; @@ -688,28 +688,37 @@ fn load_program( } pub(crate) fn query( - target_fd: BorrowedFd<'_>, + target_fd: Option>, + target_ifindex: Option<&str>, attach_type: bpf_attach_type, query_flags: u32, attach_flags: &mut Option, -) -> Result, ProgramError> { +) -> Result<(u64, Vec), ProgramError> { + assert!(target_fd.is_none() || target_ifindex.is_none()); let mut prog_ids = vec![0u32; 64]; let mut prog_cnt = prog_ids.len() as u32; + let mut revision = 0; let mut retries = 0; + let target_ifindex = target_ifindex.map(|name| { + let c_interface = CString::new(name).unwrap(); + unsafe { if_nametoindex(c_interface.as_ptr()) } + }); loop { match bpf_prog_query( - target_fd.as_fd().as_raw_fd(), + target_fd.map(|fd| fd.as_fd().as_raw_fd()), + target_ifindex, 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..c3885966 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, SyscallError, }, util::{ifindex_from_ifname, tc_handler_make, KernelVersion}, VerifierLogLevel, @@ -340,6 +341,45 @@ 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 prog_ids = query( + None, + Some(interface), + attach_type.tcx_attach_type()?, + 0, + &mut None, + )?; + + let prog_infos: Result, ProgramError> = prog_ids + .1 + .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((prog_ids.0, prog_infos?)) + } } #[derive(Debug, Hash, Eq, PartialEq)] diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 6fb99597..af9aefd1 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -498,25 +498,33 @@ pub(crate) fn bpf_prog_detach( Ok(()) } +#[allow(clippy::too_many_arguments)] pub(crate) fn bpf_prog_query( - target_fd: RawFd, + target_fd: Option, + target_ifindex: Option, 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; + if let Some(target_ifindex) = target_ifindex { + attr.query.__bindgen_anon_1.target_ifindex = target_ifindex; + } + if let Some(target_fd) = target_fd { + attr.query.__bindgen_anon_1.target_fd = target_fd as u32; + } 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..96f99e0f 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, + EbpfLoader, }; use test_log::test; @@ -25,110 +20,107 @@ 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 `EbpfLoader` and the `SchedClassifierLink` as both + // items must remain in scope for the duration of the test. macro_rules! attach_program_with_linkorder { - ($name:literal,$link_order:expr) => {{ + ($link_order:expr) => {{ let mut loader = EbpfLoader::new().load(crate::TCX).unwrap(); let program: &mut SchedClassifier = loader.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)); + (loader, 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 = vec![ + 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(), - ) - }) - ); - // TODO: Add code here to automatically verify the order after the API based - // on the BPF_PROG_QUERY syscall is implemented. + .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!(got_order.len(), expected_order.len()); + assert_eq!(revision, 10); + + got_order + .iter() + .zip(expected_order.iter()) + .for_each(|(got, expected)| { + assert_eq!( + got.id(), + *expected, + "unexpected program order got: {:?} expected: {:?}", + got, + expected + ); + }); }