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 <dave@dtucker.co.uk>
pull/921/head
Dave Tucker 14 hours ago
parent f5d4fdedba
commit c5bc4ba0da

@ -8,7 +8,7 @@ use crate::{
bpf_prog_get_fd_by_id, define_link_wrapper, load_program, query, CgroupAttachMode, FdLink, bpf_prog_get_fd_by_id, define_link_wrapper, load_program, query, CgroupAttachMode, FdLink,
Link, ProgAttachLink, ProgramData, ProgramError, ProgramFd, Link, ProgAttachLink, ProgramData, ProgramError, ProgramFd,
}, },
sys::{bpf_link_create, LinkTarget, SyscallError}, sys::{bpf_link_create, LinkTarget, ProgQueryTarget, SyscallError},
util::KernelVersion, util::KernelVersion,
}; };
@ -120,7 +120,12 @@ impl CgroupDevice {
/// Queries the cgroup for attached programs. /// Queries the cgroup for attached programs.
pub fn query<T: AsFd>(target_fd: T) -> Result<Vec<CgroupDeviceLink>, ProgramError> { pub fn query<T: AsFd>(target_fd: T) -> Result<Vec<CgroupDeviceLink>, ProgramError> {
let target_fd = target_fd.as_fd(); 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 prog_ids
.into_iter() .into_iter()

@ -7,7 +7,7 @@ use crate::{
load_program, query, CgroupAttachMode, Link, ProgramData, ProgramError, ProgramFd, load_program, query, CgroupAttachMode, Link, ProgramData, ProgramError, ProgramFd,
ProgramInfo, 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. /// 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. /// Queries the lirc device for attached programs.
pub fn query<T: AsFd>(target_fd: T) -> Result<Vec<LircLink>, ProgramError> { pub fn query<T: AsFd>(target_fd: T) -> Result<Vec<LircLink>, ProgramError> {
let target_fd = target_fd.as_fd(); 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 prog_ids
.into_iter() .into_iter()

@ -73,7 +73,7 @@ use std::{
ffi::CString, ffi::CString,
io, io,
num::TryFromIntError, num::TryFromIntError,
os::fd::{AsFd, AsRawFd, BorrowedFd}, os::fd::{AsFd, BorrowedFd},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
@ -122,7 +122,7 @@ use crate::{
sys::{ sys::{
bpf_btf_get_fd_by_id, bpf_get_object, bpf_link_get_fd_by_id, bpf_link_get_info_by_fd, 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, 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, util::KernelVersion,
VerifierLogLevel, VerifierLogLevel,
@ -688,28 +688,30 @@ fn load_program<T: Link>(
} }
pub(crate) fn query( pub(crate) fn query(
target_fd: BorrowedFd<'_>, target: ProgQueryTarget<'_>,
attach_type: bpf_attach_type, attach_type: bpf_attach_type,
query_flags: u32, query_flags: u32,
attach_flags: &mut Option<u32>, attach_flags: &mut Option<u32>,
) -> Result<Vec<u32>, ProgramError> { ) -> Result<(u64, Vec<u32>), ProgramError> {
let mut prog_ids = vec![0u32; 64]; let mut prog_ids = vec![0u32; 64];
let mut prog_cnt = prog_ids.len() as u32; let mut prog_cnt = prog_ids.len() as u32;
let mut revision = 0;
let mut retries = 0; let mut retries = 0;
loop { loop {
match bpf_prog_query( match bpf_prog_query(
target_fd.as_fd().as_raw_fd(), &target,
attach_type, attach_type,
query_flags, query_flags,
attach_flags.as_mut(), attach_flags.as_mut(),
&mut prog_ids, &mut prog_ids,
&mut prog_cnt, &mut prog_cnt,
&mut revision,
) { ) {
Ok(_) => { Ok(_) => {
prog_ids.resize(prog_cnt as usize, 0); prog_ids.resize(prog_cnt as usize, 0);
return Ok(prog_ids); return Ok((revision, prog_ids));
} }
Err((_, io_error)) => { Err((_, io_error)) => {
if retries == 0 && io_error.raw_os_error() == Some(ENOSPC) { if retries == 0 && io_error.raw_os_error() == Some(ENOSPC) {

@ -8,7 +8,7 @@ use std::{
use thiserror::Error; use thiserror::Error;
use super::FdLink; use super::{FdLink, ProgramInfo};
use crate::{ use crate::{
generated::{ generated::{
bpf_attach_type::{self, BPF_TCX_EGRESS, BPF_TCX_INGRESS}, 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, TC_H_CLSACT, TC_H_MIN_EGRESS, TC_H_MIN_INGRESS,
}, },
programs::{ programs::{
define_link_wrapper, load_program, Link, LinkError, LinkOrder, ProgramData, ProgramError, define_link_wrapper, load_program, query, Link, LinkError, LinkOrder, ProgramData,
ProgramError,
}, },
sys::{ sys::{
bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, netlink_find_filter_with_name, bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, bpf_prog_get_fd_by_id,
netlink_qdisc_add_clsact, netlink_qdisc_attach, netlink_qdisc_detach, LinkTarget, netlink_find_filter_with_name, netlink_qdisc_add_clsact, netlink_qdisc_attach,
SyscallError, netlink_qdisc_detach, LinkTarget, ProgQueryTarget, SyscallError,
}, },
util::{ifindex_from_ifname, tc_handler_make, KernelVersion}, util::{ifindex_from_ifname, tc_handler_make, KernelVersion},
VerifierLogLevel, VerifierLogLevel,
@ -340,6 +341,46 @@ impl SchedClassifier {
let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
Ok(Self { data }) 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<ProgramInfo>), 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::<ProgramInfo, ProgramError>(prog_info)
})
.collect::<Result<_, _>>()?;
Ok((revision, prog_infos))
}
} }
#[derive(Debug, Hash, Eq, PartialEq)] #[derive(Debug, Hash, Eq, PartialEq)]

@ -498,25 +498,39 @@ pub(crate) fn bpf_prog_detach(
Ok(()) Ok(())
} }
#[derive(Debug)]
pub(crate) enum ProgQueryTarget<'a> {
Fd(BorrowedFd<'a>),
IfIndex(u32),
}
pub(crate) fn bpf_prog_query( pub(crate) fn bpf_prog_query(
target_fd: RawFd, target: &ProgQueryTarget<'_>,
attach_type: bpf_attach_type, attach_type: bpf_attach_type,
query_flags: u32, query_flags: u32,
attach_flags: Option<&mut u32>, attach_flags: Option<&mut u32>,
prog_ids: &mut [u32], prog_ids: &mut [u32],
prog_cnt: &mut u32, prog_cnt: &mut u32,
revision: &mut u64,
) -> SysResult<i64> { ) -> SysResult<i64> {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() }; let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
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.attach_type = attach_type as u32;
attr.query.query_flags = query_flags; attr.query.query_flags = query_flags;
attr.query.__bindgen_anon_2.prog_cnt = prog_ids.len() as u32; attr.query.__bindgen_anon_2.prog_cnt = prog_ids.len() as u32;
attr.query.prog_ids = prog_ids.as_mut_ptr() as u64; attr.query.prog_ids = prog_ids.as_mut_ptr() as u64;
let ret = sys_bpf(bpf_cmd::BPF_PROG_QUERY, &mut attr); let ret = sys_bpf(bpf_cmd::BPF_PROG_QUERY, &mut attr);
*prog_cnt = unsafe { attr.query.__bindgen_anon_2.prog_cnt }; *prog_cnt = unsafe { attr.query.__bindgen_anon_2.prog_cnt };
*revision = unsafe { attr.query.revision };
if let Some(attach_flags) = attach_flags { if let Some(attach_flags) = attach_flags {
*attach_flags = unsafe { attr.query.attach_flags }; *attach_flags = unsafe { attr.query.attach_flags };

@ -1,12 +1,7 @@
use std::collections::HashMap;
use aya::{ use aya::{
programs::{ programs::{tc::TcAttachOptions, LinkOrder, ProgramId, SchedClassifier, TcAttachType},
tc::{SchedClassifierLink, TcAttachOptions},
LinkOrder, ProgramId, SchedClassifier, TcAttachType,
},
util::KernelVersion, util::KernelVersion,
Ebpf, EbpfLoader, Ebpf,
}; };
use test_log::test; use test_log::test;
@ -25,110 +20,97 @@ async fn tcx() {
// We need a dedicated `Ebpf` instance for each program that we load // 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 // since TCX does not allow the same program ID to be attached multiple
// times to the same interface/direction. // 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 { macro_rules! attach_program_with_linkorder {
($name:literal,$link_order:expr) => {{ ($link_order:expr) => {{
let mut loader = EbpfLoader::new().load(crate::TCX).unwrap(); let mut ebpf = Ebpf::load(crate::TCX).unwrap();
let program: &mut SchedClassifier = 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(); program.load().unwrap();
let options = TcAttachOptions::TcxOrder($link_order);
let link_id = program let link_id = program
.attach_with_options("lo", TcAttachType::Ingress, options) .attach_with_options(
"lo",
TcAttachType::Ingress,
TcAttachOptions::TcxOrder($link_order),
)
.unwrap(); .unwrap();
let link = program.take_link(link_id).unwrap(); (ebpf, link_id)
attached_programs.insert($name, (loader, link));
}}; }};
} }
// TODO: Assert in position 4 at the end of the test. let (default, _) = attach_program_with_linkorder!(LinkOrder::default());
attach_program_with_linkorder!("default", LinkOrder::default()); let (first, _) = attach_program_with_linkorder!(LinkOrder::first());
// TODO: Assert in position 1 at the end of the test. let (mut last, last_link_id) = attach_program_with_linkorder!(LinkOrder::last());
attach_program_with_linkorder!("first", LinkOrder::first());
// TODO: Assert in position 7 at the end of the test. let default_prog: &SchedClassifier = default.program("tcx_next").unwrap().try_into().unwrap();
attach_program_with_linkorder!("last", LinkOrder::last()); let first_prog: &SchedClassifier = first.program("tcx_next").unwrap().try_into().unwrap();
// TODO: Assert in position 6 at the end of the test. let last_prog: &mut SchedClassifier = last.program_mut("tcx_next").unwrap().try_into().unwrap();
attach_program_with_linkorder!(
"before_last", let last_link = last_prog.take_link(last_link_id).unwrap();
LinkOrder::before_link(&attached_programs.get("last").unwrap().1).unwrap()
); let (before_last, _) =
// TODO: Assert in position 8 at the end of the test. attach_program_with_linkorder!(LinkOrder::before_link(&last_link).unwrap());
attach_program_with_linkorder!( let (after_last, _) =
"after_last", attach_program_with_linkorder!(LinkOrder::after_link(&last_link).unwrap());
LinkOrder::after_link(&attached_programs.get("last").unwrap().1).unwrap()
); let (before_default, _) =
// TODO: Assert in position 3 at the end of the test. attach_program_with_linkorder!(LinkOrder::before_program(default_prog).unwrap());
attach_program_with_linkorder!( let (after_default, _) =
"before_default", attach_program_with_linkorder!(LinkOrder::after_program(default_prog).unwrap());
LinkOrder::before_program(
TryInto::<&SchedClassifier>::try_into( let (before_first, _) = attach_program_with_linkorder!(LinkOrder::before_program_id(unsafe {
attached_programs ProgramId::new(first_prog.info().unwrap().id())
.get("default") }));
.unwrap() let (after_first, _) = attach_program_with_linkorder!(LinkOrder::after_program_id(unsafe {
.0 ProgramId::new(first_prog.info().unwrap().id())
.program("tcx_next") }));
.unwrap(),
) let expected_order = [
before_first
.program("tcx_next")
.unwrap() .unwrap()
) .info()
.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(),
)
.unwrap() .unwrap()
) .id(),
.unwrap() first_prog.info().unwrap().id(),
); after_first
// TODO: Assert in position 0 at the end of the test. .program("tcx_next")
attach_program_with_linkorder!( .unwrap()
"before_first", .info()
LinkOrder::before_program_id(unsafe { .unwrap()
ProgramId::new( .id(),
TryInto::<&SchedClassifier>::try_into( before_default
attached_programs .program("tcx_next")
.get("first") .unwrap()
.unwrap() .info()
.0 .unwrap()
.program("tcx_next") .id(),
.unwrap(), default_prog.info().unwrap().id(),
) after_default
.unwrap() .program("tcx_next")
.info() .unwrap()
.unwrap() .info()
.id(), .unwrap()
) .id(),
}) before_last
); .program("tcx_next")
// TODO: Assert in position 2 at the end of the test. .unwrap()
attach_program_with_linkorder!( .info()
"after_first", .unwrap()
LinkOrder::after_program_id(unsafe { .id(),
ProgramId::new( last_prog.info().unwrap().id(),
TryInto::<&SchedClassifier>::try_into( after_last.program("tcx_next").unwrap().info().unwrap().id(),
attached_programs ];
.get("first")
.unwrap() let (revision, got_order) = SchedClassifier::query_tcx("lo", TcAttachType::Ingress).unwrap();
.0 assert_eq!(revision, (expected_order.len() + 1) as u64);
.program("tcx_next") assert_eq!(
.unwrap(), got_order.iter().map(|p| p.id()).collect::<Vec<_>>(),
) expected_order
.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.
} }

Loading…
Cancel
Save