From 047a4505d5533f924fc849dbf5633b451e0fe689 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Thu, 19 Sep 2024 14:43:44 +0100 Subject: [PATCH] feat(aya): Add SchedClassifier:query_tcx() API This adds SchedClassifier::query_tcx() API that is able to return an ordered list of ProgramInfo (and revision) of an interface that has TCX programs attached. This is useful to verify the ordering of the programs in our integration tests, but also to for #1032. Additionally tidies up the macro used for TCX testing and adds assertions using this new API. Signed-off-by: Dave Tucker --- aya/src/programs/cgroup_device.rs | 2 +- aya/src/programs/lirc_mode2.rs | 2 +- aya/src/programs/mod.rs | 19 ++- aya/src/programs/tc.rs | 50 ++++++- aya/src/sys/bpf.rs | 14 +- macro.patch | 0 test/integration-test/src/tests/tcx.rs | 194 ++++++++++++------------- 7 files changed, 165 insertions(+), 116 deletions(-) delete mode 100644 macro.patch 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 + ); + }); }