doc(aya): document tcx link pinning for atomic program replacement

Add documentation and integration test for SchedClassifier link pinning,
which enables zero-downtime program updates in production environments.

- Add link pinning example to SchedClassifier::attach_with_options()
  showing atomic replacement workflow for TCX mode (kernel >= 6.6).
- Add pin_tcx_link() integration test verifying link persistence
  across program unload and atomic replacement capability.
pull/1396/head
Sven Cowart 1 month ago
parent 28ae4b9826
commit 6e800b2595

@ -205,12 +205,47 @@ impl SchedClassifier {
/// ///
/// The returned value can be used to detach, see [SchedClassifier::detach]. /// The returned value can be used to detach, see [SchedClassifier::detach].
/// ///
/// # Link Pinning (TCX mode, kernel >= 6.6)
///
/// Links can be pinned to bpffs for atomic replacement across process restarts.
///
/// ```no_run
/// # use std::{io, path::Path};
/// # use aya::programs::{tc, SchedClassifier, TcAttachType, tc::TcAttachOptions, LinkOrder, links::{FdLink, PinnedLink}, LinkError};
/// # use aya::sys::SyscallError;
/// # let mut bpf = aya::Ebpf::load(&[])?;
/// # let prog: &mut SchedClassifier = bpf.program_mut("prog").unwrap().try_into()?;
/// # prog.load()?;
/// let pin_path = "/sys/fs/bpf/my_link";
///
/// let link_id = match PinnedLink::from_pin(pin_path) {
/// Ok(old) => {
/// let link = FdLink::from(old).try_into()?;
/// prog.attach_to_link(link)?
/// }
/// Err(LinkError::SyscallError(SyscallError { io_error, .. }))
/// if io_error.kind() == io::ErrorKind::NotFound =>
/// {
/// prog.attach_with_options(
/// "eth0",
/// TcAttachType::Ingress,
/// TcAttachOptions::TcxOrder(LinkOrder::default()),
/// )?
/// }
/// Err(e) => return Err(e.into()),
/// };
///
/// let link = prog.take_link(link_id)?;
/// let fd_link: FdLink = link.try_into()?;
/// fd_link.pin(pin_path)?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Errors /// # Errors
/// ///
/// [`TcError::NetlinkError`] is returned if attaching fails. A common cause /// [`TcError::NetlinkError`] is returned if attaching fails. A common cause
/// of failure is not having added the `clsact` qdisc to the given /// of failure is not having added the `clsact` qdisc to the given
/// interface, see [`qdisc_add_clsact`] /// interface, see [`qdisc_add_clsact`].
///
pub fn attach_with_options( pub fn attach_with_options(
&mut self, &mut self,
interface: &str, interface: &str,

@ -6,11 +6,13 @@ use aya::{
maps::Array, maps::Array,
pin::PinError, pin::PinError,
programs::{ programs::{
FlowDissector, KProbe, ProbeKind, Program, ProgramError, TracePoint, UProbe, Xdp, XdpFlags, FlowDissector, KProbe, LinkOrder, ProbeKind, Program, ProgramError, SchedClassifier,
TcAttachType, TracePoint, UProbe, Xdp, XdpFlags,
flow_dissector::{FlowDissectorLink, FlowDissectorLinkId}, flow_dissector::{FlowDissectorLink, FlowDissectorLinkId},
kprobe::{KProbeLink, KProbeLinkId}, kprobe::{KProbeLink, KProbeLinkId},
links::{FdLink, LinkError, PinnedLink}, links::{FdLink, LinkError, PinnedLink},
loaded_links, loaded_programs, loaded_links, loaded_programs,
tc::TcAttachOptions,
trace_point::{TracePointLink, TracePointLinkId}, trace_point::{TracePointLink, TracePointLinkId},
uprobe::{UProbeLink, UProbeLinkId}, uprobe::{UProbeLink, UProbeLinkId},
xdp::{XdpLink, XdpLinkId}, xdp::{XdpLink, XdpLinkId},
@ -394,6 +396,58 @@ fn pin_link() {
assert_unloaded(program_name); assert_unloaded(program_name);
} }
#[test_log::test]
fn pin_tcx_link() {
// TCX links require kernel >= 6.6
let kernel_version = KernelVersion::current().unwrap();
if kernel_version < KernelVersion::new(6, 6, 0) {
eprintln!("skipping pin_tcx_link test on kernel {kernel_version:?}");
return;
}
use crate::utils::NetNsGuard;
let _netns = NetNsGuard::new();
let program_name = "tcx_next";
let pin_path = "/sys/fs/bpf/aya-tcx-test-lo";
let mut bpf = Ebpf::load(crate::TCX).unwrap();
let prog: &mut SchedClassifier = bpf.program_mut(program_name).unwrap().try_into().unwrap();
prog.load().unwrap();
let link_id = prog
.attach_with_options(
"lo",
TcAttachType::Ingress,
TcAttachOptions::TcxOrder(LinkOrder::default()),
)
.unwrap();
let link = prog.take_link(link_id).unwrap();
assert_loaded(program_name);
let fd_link: FdLink = link.try_into().unwrap();
fd_link.pin(pin_path).unwrap();
// Because of the pin, the program is still attached
prog.unload().unwrap();
assert_loaded(program_name);
// Load a new program and atomically replace the old one using attach_to_link
let mut bpf = Ebpf::load(crate::TCX).unwrap();
let prog: &mut SchedClassifier = bpf.program_mut(program_name).unwrap().try_into().unwrap();
prog.load().unwrap();
let old_link = PinnedLink::from_pin(pin_path).unwrap();
let link = FdLink::from(old_link).try_into().unwrap();
let _link_id = prog.attach_to_link(link).unwrap();
assert_loaded(program_name);
// Clean up: remove the stale pin file and drop the bpf instance (which drops the program and link)
remove_file(pin_path).unwrap();
drop(bpf);
assert_unloaded(program_name);
}
trait PinProgramOps { trait PinProgramOps {
fn pin<P: AsRef<Path>>(&mut self, path: P) -> Result<(), PinError>; fn pin<P: AsRef<Path>>(&mut self, path: P) -> Result<(), PinError>;
fn unpin(&mut self) -> Result<(), std::io::Error>; fn unpin(&mut self) -> Result<(), std::io::Error>;

Loading…
Cancel
Save