mirror of https://github.com/aya-rs/aya
add basic tcx integration tests
Add basic tcx integration tests to ensure the new Mprog API is working as expected. Signed-off-by: astoycos <astoycos@gmail.com>reviewable/pr921/r7
parent
c886d2a0c9
commit
9d3af47152
@ -0,0 +1,79 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use core::mem;
|
||||||
|
|
||||||
|
use aya_ebpf::{
|
||||||
|
bindings::tcx_action_base::{TCX_NEXT, TCX_PASS},
|
||||||
|
macros::classifier,
|
||||||
|
programs::TcContext,
|
||||||
|
};
|
||||||
|
use aya_log_ebpf::info;
|
||||||
|
use network_types::{
|
||||||
|
eth::{EthHdr, EtherType},
|
||||||
|
ip::{IpProto, Ipv4Hdr},
|
||||||
|
udp::UdpHdr,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
static ORDER: i32 = 0;
|
||||||
|
|
||||||
|
// Gives us raw pointers to a specific offset in the packet
|
||||||
|
#[inline(always)]
|
||||||
|
unsafe fn ptr_at<T>(ctx: &TcContext, offset: usize) -> Result<*mut T, i64> {
|
||||||
|
let start = ctx.data();
|
||||||
|
let end = ctx.data_end();
|
||||||
|
let len = mem::size_of::<T>();
|
||||||
|
|
||||||
|
if start + offset + len > end {
|
||||||
|
return Err(TCX_PASS.into());
|
||||||
|
}
|
||||||
|
Ok((start + offset) as *mut T)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[classifier]
|
||||||
|
pub fn tcx_order(ctx: TcContext) -> i32 {
|
||||||
|
match try_tcxtest(ctx) {
|
||||||
|
Ok(ret) => ret,
|
||||||
|
Err(_ret) => TCX_PASS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_tcxtest(ctx: TcContext) -> Result<i32, i64> {
|
||||||
|
let eth_hdr: *const EthHdr = unsafe { ptr_at(&ctx, 0) }?;
|
||||||
|
let order = unsafe { core::ptr::read_volatile(&ORDER) };
|
||||||
|
match unsafe { *eth_hdr }.ether_type {
|
||||||
|
EtherType::Ipv4 => {
|
||||||
|
let ipv4_hdr: *const Ipv4Hdr = unsafe { ptr_at(&ctx, EthHdr::LEN)? };
|
||||||
|
let saddr = u32::from_be(unsafe { (*ipv4_hdr).src_addr });
|
||||||
|
let daddr = u32::from_be(unsafe { (*ipv4_hdr).dst_addr });
|
||||||
|
match unsafe { (*ipv4_hdr).proto } {
|
||||||
|
IpProto::Udp => {
|
||||||
|
let udphdr: *const UdpHdr =
|
||||||
|
unsafe { ptr_at(&ctx, EthHdr::LEN + Ipv4Hdr::LEN) }?;
|
||||||
|
let dport = u16::from_be(unsafe { (*udphdr).dest });
|
||||||
|
let sport = u16::from_be(unsafe { (*udphdr).source });
|
||||||
|
info!(
|
||||||
|
&ctx,
|
||||||
|
"order: {}, cookie: ({:i}, {:i}, {}, {})",
|
||||||
|
order,
|
||||||
|
daddr,
|
||||||
|
saddr,
|
||||||
|
dport,
|
||||||
|
sport
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(TCX_NEXT)
|
||||||
|
}
|
||||||
|
_ => Ok(TCX_PASS),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(TCX_PASS),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
@ -0,0 +1,171 @@
|
|||||||
|
use std::{
|
||||||
|
net::UdpSocket,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use aya::{
|
||||||
|
programs::{tc::TcAttachOptions, LinkOrder, SchedClassifier, TcAttachType},
|
||||||
|
util::KernelVersion,
|
||||||
|
Ebpf, EbpfLoader,
|
||||||
|
};
|
||||||
|
use aya_log::EbpfLogger;
|
||||||
|
use chrono::Utc;
|
||||||
|
use log::{debug, Record};
|
||||||
|
use test_log::test;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
tests::log::{CapturedLog, TestingLogger},
|
||||||
|
utils::NetNsGuard,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn setup_logs(loader: &mut Ebpf, logs: &Arc<Mutex<Vec<CapturedLog<'static>>>>) {
|
||||||
|
let captured_logs = logs.clone();
|
||||||
|
EbpfLogger::init_with_logger(
|
||||||
|
loader,
|
||||||
|
TestingLogger {
|
||||||
|
log: move |record: &Record| {
|
||||||
|
let mut logs = captured_logs.lock().unwrap();
|
||||||
|
logs.push(CapturedLog {
|
||||||
|
body: format!("{}", record.args()).into(),
|
||||||
|
level: record.level(),
|
||||||
|
target: record.target().to_string().into(),
|
||||||
|
timestamp: Some(Utc::now()),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test(tokio::test)]
|
||||||
|
async fn tcx_ordering() {
|
||||||
|
let kernel_version = KernelVersion::current().unwrap();
|
||||||
|
if kernel_version < KernelVersion::new(6, 6, 0) {
|
||||||
|
eprintln!("skipping tcx_ordering test on kernel {kernel_version:?}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _netns = NetNsGuard::new();
|
||||||
|
|
||||||
|
let mut program0 = EbpfLoader::new()
|
||||||
|
.set_global("ORDER", &0, true)
|
||||||
|
.load(crate::TCX)
|
||||||
|
.unwrap();
|
||||||
|
let mut program1 = EbpfLoader::new()
|
||||||
|
.set_global("ORDER", &1, true)
|
||||||
|
.load(crate::TCX)
|
||||||
|
.unwrap();
|
||||||
|
let mut program2 = EbpfLoader::new()
|
||||||
|
.set_global("ORDER", &2, true)
|
||||||
|
.load(crate::TCX)
|
||||||
|
.unwrap();
|
||||||
|
let mut program3 = EbpfLoader::new()
|
||||||
|
.set_global("ORDER", &3, true)
|
||||||
|
.load(crate::TCX)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let logs0: Arc<Mutex<Vec<CapturedLog>>> = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
setup_logs(&mut program0, &logs0);
|
||||||
|
|
||||||
|
let logs1 = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
setup_logs(&mut program1, &logs1);
|
||||||
|
|
||||||
|
let logs2 = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
setup_logs(&mut program2, &logs2);
|
||||||
|
|
||||||
|
let logs3 = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
setup_logs(&mut program3, &logs3);
|
||||||
|
|
||||||
|
let prog0: &mut SchedClassifier = program0
|
||||||
|
.program_mut("tcx_order")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
prog0.load().unwrap();
|
||||||
|
|
||||||
|
let prog1: &mut SchedClassifier = program1
|
||||||
|
.program_mut("tcx_order")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
prog1.load().unwrap();
|
||||||
|
|
||||||
|
let prog2: &mut SchedClassifier = program2
|
||||||
|
.program_mut("tcx_order")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
prog2.load().unwrap();
|
||||||
|
|
||||||
|
let prog3: &mut SchedClassifier = program3
|
||||||
|
.program_mut("tcx_order")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
prog3.load().unwrap();
|
||||||
|
|
||||||
|
// Test LinkOrder::first()
|
||||||
|
let options = TcAttachOptions::tcxoptions(LinkOrder::first());
|
||||||
|
prog0
|
||||||
|
.attach_with_options("lo", TcAttachType::Ingress, options)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Test LinkOrder::after_program()
|
||||||
|
let order = LinkOrder::after_program(prog0).unwrap();
|
||||||
|
let options = TcAttachOptions::tcxoptions(order);
|
||||||
|
let prog1_link_id = prog1
|
||||||
|
.attach_with_options("lo", TcAttachType::Ingress, options)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let prog1_link = prog1.take_link(prog1_link_id).unwrap();
|
||||||
|
|
||||||
|
// Test LinkOrder::after_link()
|
||||||
|
let order = LinkOrder::after_link(&prog1_link).unwrap();
|
||||||
|
let options = TcAttachOptions::tcxoptions(order);
|
||||||
|
prog2
|
||||||
|
.attach_with_options("lo", TcAttachType::Ingress, options)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Test LinkOrder::last()
|
||||||
|
let options = TcAttachOptions::tcxoptions(LinkOrder::last());
|
||||||
|
prog3
|
||||||
|
.attach_with_options("lo", TcAttachType::Ingress, options)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
const PAYLOAD: &str = "hello tcx";
|
||||||
|
|
||||||
|
let sock = UdpSocket::bind("127.0.0.1:1778").unwrap();
|
||||||
|
let addr = sock.local_addr().unwrap();
|
||||||
|
sock.set_read_timeout(Some(Duration::from_secs(60)))
|
||||||
|
.unwrap();
|
||||||
|
// We only need to send data since we're attaching tx programs to the ingress hook
|
||||||
|
sock.send_to(PAYLOAD.as_bytes(), addr).unwrap();
|
||||||
|
|
||||||
|
// Allow logs to populate
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
|
||||||
|
let log0 = logs0.lock().unwrap();
|
||||||
|
let log1 = logs1.lock().unwrap();
|
||||||
|
let log2 = logs2.lock().unwrap();
|
||||||
|
let log3 = logs3.lock().unwrap();
|
||||||
|
|
||||||
|
debug!("log0: {:?}", log0.first());
|
||||||
|
debug!("log1: {:?}", log1.first());
|
||||||
|
debug!("log2: {:?}", log2.first());
|
||||||
|
debug!("log3: {:?}", log3.first());
|
||||||
|
|
||||||
|
// sort logs by timestamp
|
||||||
|
let mut sorted_logs = [
|
||||||
|
log0.first().unwrap(),
|
||||||
|
log1.first().unwrap(),
|
||||||
|
log2.first().unwrap(),
|
||||||
|
log3.first().unwrap(),
|
||||||
|
];
|
||||||
|
sorted_logs.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
|
||||||
|
|
||||||
|
assert!(sorted_logs[0].body.contains("order: 0"));
|
||||||
|
assert!(sorted_logs[1].body.contains("order: 1"));
|
||||||
|
assert!(sorted_logs[2].body.contains("order: 2"));
|
||||||
|
assert!(sorted_logs[3].body.contains("order: 3"));
|
||||||
|
}
|
Loading…
Reference in New Issue