diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs index 58a0ecbf..1348d62c 100644 --- a/aya/src/programs/tc.rs +++ b/aya/src/programs/tc.rs @@ -205,12 +205,37 @@ impl SchedClassifier { /// /// 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::path::Path; + /// # use aya::programs::{tc, SchedClassifier, TcAttachType, tc::TcAttachOptions, LinkOrder, links::{FdLink, PinnedLink}}; + /// # 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 = if Path::new(pin_path).exists() { + /// let old = PinnedLink::from_pin(pin_path)?; + /// prog.attach_to_link(FdLink::from(old).try_into()?)? // atomic replacement + /// } else { + /// prog.attach_with_options("eth0", TcAttachType::Ingress, + /// TcAttachOptions::TcxOrder(LinkOrder::default()))? + /// }; + /// + /// let link = prog.take_link(link_id)?; + /// let fd_link: FdLink = link.try_into()?; + /// fd_link.pin(pin_path)?; + /// # Ok::<(), Box>(()) + /// ``` + /// /// # Errors /// /// [`TcError::NetlinkError`] is returned if attaching fails. A common cause /// 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( &mut self, interface: &str, diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs index 26046824..a1a3e5ca 100644 --- a/test/integration-test/src/tests/load.rs +++ b/test/integration-test/src/tests/load.rs @@ -6,11 +6,13 @@ use aya::{ maps::Array, pin::PinError, 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}, kprobe::{KProbeLink, KProbeLinkId}, links::{FdLink, LinkError, PinnedLink}, loaded_links, loaded_programs, + tc::TcAttachOptions, trace_point::{TracePointLink, TracePointLinkId}, uprobe::{UProbeLink, UProbeLinkId}, xdp::{XdpLink, XdpLinkId}, @@ -394,6 +396,49 @@ fn pin_link() { 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_tc_link test on kernel {kernel_version:?}"); + return; + } + + use crate::utils::NetNsGuard; + let _netns = NetNsGuard::new(); + + let program_name = "tcx_next"; + 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(); + let pinned = fd_link.pin("/sys/fs/bpf/aya-tcx-test-lo").unwrap(); + + // because of the pin, the program is still attached + prog.unload().unwrap(); + assert_loaded(program_name); + + // delete the pin, but the program is still attached + let new_link = pinned.unpin().unwrap(); + assert_loaded(program_name); + + // finally when new_link is dropped we're detached + drop(new_link); + assert_unloaded(program_name); +} + trait PinProgramOps { fn pin>(&mut self, path: P) -> Result<(), PinError>; fn unpin(&mut self) -> Result<(), std::io::Error>;