From ac07608b7922a545cd1de1996b66dcbdeb7fbbe1 Mon Sep 17 00:00:00 2001 From: Andre Fredette Date: Fri, 14 Oct 2022 16:46:36 -0400 Subject: [PATCH 1/4] Support using handle in tc programs Implements step 1 of https://github.com/aya-rs/aya/issues/414. - Adds handle to the SchedClassifier attach API - Saves handle in the TcLink sruct and uses it when detaching programs NOTE: this changes the API, so it will require a bump in the Aya version. Signed-off-by: Andre Fredette --- .gitignore | 1 + aya/src/programs/tc.rs | 37 ++++++++++++++++++++++++++----------- aya/src/sys/netlink.rs | 34 +++++++++++++++++----------------- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index d194918f..0248fbf2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ libbpf/ !.vscode/settings.json site/ header.html +.idea/ diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs index 00a82f06..7748a845 100644 --- a/aya/src/programs/tc.rs +++ b/aya/src/programs/tc.rs @@ -63,7 +63,7 @@ pub enum TcAttachType { /// /// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?; /// prog.load()?; -/// prog.attach("eth0", TcAttachType::Ingress, 0)?; +/// prog.attach("eth0", TcAttachType::Ingress, 0, 0)?; /// /// # Ok::<(), Error>(()) /// ``` @@ -111,6 +111,9 @@ impl SchedClassifier { /// 0 means let the system choose the next highest priority, or 49152 if no filters exist yet. /// All other values in the range are taken as an explicit priority setting (aka "preference"). /// + /// `handle` is used to uniquely identify a program at a given priority level. + /// If set to 0, the system will choose a handle. + /// /// The returned value can be used to detach, see [SchedClassifier::detach]. /// /// # Errors @@ -124,12 +127,20 @@ impl SchedClassifier { interface: &str, attach_type: TcAttachType, priority: u16, + handle: u32, ) -> Result { let prog_fd = self.data.fd_or_err()?; let if_index = ifindex_from_ifname(interface) .map_err(|io_error| TcError::NetlinkError { io_error })?; - let priority = unsafe { - netlink_qdisc_attach(if_index as i32, &attach_type, prog_fd, &self.name, priority) + let (priority, handle) = unsafe { + netlink_qdisc_attach( + if_index as i32, + &attach_type, + prog_fd, + &self.name, + priority, + handle, + ) } .map_err(|io_error| TcError::NetlinkError { io_error })?; @@ -137,6 +148,7 @@ impl SchedClassifier { if_index: if_index as i32, attach_type, priority, + handle, })) } @@ -160,25 +172,28 @@ impl SchedClassifier { } #[derive(Debug, Hash, Eq, PartialEq)] -pub(crate) struct TcLinkId(i32, TcAttachType, u16); +pub(crate) struct TcLinkId(i32, TcAttachType, u16, u32); #[derive(Debug)] struct TcLink { if_index: i32, attach_type: TcAttachType, priority: u16, + handle: u32, } impl Link for TcLink { type Id = TcLinkId; fn id(&self) -> Self::Id { - TcLinkId(self.if_index, self.attach_type, self.priority) + TcLinkId(self.if_index, self.attach_type, self.priority, self.handle) } fn detach(self) -> Result<(), ProgramError> { - unsafe { netlink_qdisc_detach(self.if_index, &self.attach_type, self.priority) } - .map_err(|io_error| TcError::NetlinkError { io_error })?; + unsafe { + netlink_qdisc_detach(self.if_index, &self.attach_type, self.priority, self.handle) + } + .map_err(|io_error| TcError::NetlinkError { io_error })?; Ok(()) } } @@ -233,16 +248,16 @@ fn qdisc_detach_program_fast( ) -> Result<(), io::Error> { let if_index = ifindex_from_ifname(if_name)? as i32; - let prios = unsafe { netlink_find_filter_with_name(if_index, attach_type, name)? }; - if prios.is_empty() { + let filter_info = unsafe { netlink_find_filter_with_name(if_index, attach_type, name)? }; + if filter_info.is_empty() { return Err(io::Error::new( io::ErrorKind::NotFound, name.to_string_lossy(), )); } - for prio in prios { - unsafe { netlink_qdisc_detach(if_index, &attach_type, prio)? }; + for (prio, handle) in filter_info { + unsafe { netlink_qdisc_detach(if_index, &attach_type, prio, handle)? }; } Ok(()) diff --git a/aya/src/sys/netlink.rs b/aya/src/sys/netlink.rs index 47fce1cf..b0526cc3 100644 --- a/aya/src/sys/netlink.rs +++ b/aya/src/sys/netlink.rs @@ -104,7 +104,8 @@ pub(crate) unsafe fn netlink_qdisc_attach( prog_fd: RawFd, prog_name: &CStr, priority: u16, -) -> Result { + handle: u32, +) -> Result<(u16, u32), io::Error> { let sock = NetlinkSocket::open()?; let mut req = mem::zeroed::(); @@ -117,7 +118,7 @@ pub(crate) unsafe fn netlink_qdisc_attach( nlmsg_seq: 1, }; req.tc_info.tcm_family = AF_UNSPEC as u8; - req.tc_info.tcm_handle = 0; // auto-assigned, if not provided + req.tc_info.tcm_handle = handle; // auto-assigned, if zero req.tc_info.tcm_ifindex = if_index; req.tc_info.tcm_parent = attach_type.parent(); req.tc_info.tcm_info = tc_handler_make((priority as u32) << 16, htons(ETH_P_ALL as u16) as u32); @@ -138,17 +139,14 @@ pub(crate) unsafe fn netlink_qdisc_attach( req.header.nlmsg_len += align_to(kind_len + options_len, NLA_ALIGNTO as usize) as u32; sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; - // find the RTM_NEWTFILTER reply and read the tcm_info field which we'll - // need to detach - let tc_info = match sock + // find the RTM_NEWTFILTER reply and read the tcm_info and tcm_handle fields + // which we'll need to detach + let tc_msg = match sock .recv()? .iter() .find(|reply| reply.header.nlmsg_type == RTM_NEWTFILTER) { - Some(reply) => { - let msg = ptr::read_unaligned(reply.data.as_ptr() as *const tcmsg); - msg.tcm_info - } + Some(reply) => ptr::read_unaligned(reply.data.as_ptr() as *const tcmsg), None => { // if sock.recv() succeeds we should never get here unless there's a // bug in the kernel @@ -159,14 +157,15 @@ pub(crate) unsafe fn netlink_qdisc_attach( } }; - let priority = ((tc_info & TC_H_MAJ_MASK) >> 16) as u16; - Ok(priority) + let priority = ((tc_msg.tcm_info & TC_H_MAJ_MASK) >> 16) as u16; + Ok((priority, tc_msg.tcm_handle)) } pub(crate) unsafe fn netlink_qdisc_detach( if_index: i32, attach_type: &TcAttachType, priority: u16, + handle: u32, ) -> Result<(), io::Error> { let sock = NetlinkSocket::open()?; let mut req = mem::zeroed::(); @@ -180,7 +179,7 @@ pub(crate) unsafe fn netlink_qdisc_detach( }; req.tc_info.tcm_family = AF_UNSPEC as u8; - req.tc_info.tcm_handle = 0; // auto-assigned, if not provided + req.tc_info.tcm_handle = handle; // auto-assigned, if zero req.tc_info.tcm_info = tc_handler_make((priority as u32) << 16, htons(ETH_P_ALL as u16) as u32); req.tc_info.tcm_parent = attach_type.parent(); req.tc_info.tcm_ifindex = if_index; @@ -192,11 +191,12 @@ pub(crate) unsafe fn netlink_qdisc_detach( Ok(()) } +// Returns a vector of tuple (priority, handle) for filters matching the provided parameters pub(crate) unsafe fn netlink_find_filter_with_name( if_index: i32, attach_type: TcAttachType, name: &CStr, -) -> Result, io::Error> { +) -> Result, io::Error> { let mut req = mem::zeroed::(); let nlmsg_len = mem::size_of::() + mem::size_of::(); @@ -208,14 +208,14 @@ pub(crate) unsafe fn netlink_find_filter_with_name( nlmsg_seq: 1, }; req.tc_info.tcm_family = AF_UNSPEC as u8; - req.tc_info.tcm_handle = 0; // auto-assigned, if not provided + req.tc_info.tcm_handle = 0; // auto-assigned, if zero req.tc_info.tcm_ifindex = if_index; req.tc_info.tcm_parent = attach_type.parent(); let sock = NetlinkSocket::open()?; sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; - let mut prios = Vec::new(); + let mut filter_info = Vec::new(); for msg in sock.recv()? { if msg.header.nlmsg_type != RTM_NEWTFILTER { continue; @@ -230,14 +230,14 @@ pub(crate) unsafe fn netlink_find_filter_with_name( if let Some(f_name) = opts.get(&(TCA_BPF_NAME as u16)) { if let Ok(f_name) = CStr::from_bytes_with_nul(f_name.data) { if name == f_name { - prios.push(priority); + filter_info.push((priority, tc_msg.tcm_handle)); } } } } } - Ok(prios) + Ok(filter_info) } #[repr(C)] From af3de84b081941bd3139c7089618a53dfa37ac83 Mon Sep 17 00:00:00 2001 From: Andre Fredette Date: Mon, 24 Oct 2022 17:21:18 -0400 Subject: [PATCH 2/4] Use a struct for setting priority and handle in SchedClassfier attach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use a struct called TcOptions for setting priority and handle in SchedClassifier attach struct TcOptions implements the Default trait, so for the simple use case in which the defaults are acceptable, we can call attach as follows: attach(“eth0”, TcAttachType::Ingress, TcOptions::default()) To specify all options: attach(“eth0”, TcAttachType::Ingress, TcOptions { priority: (50), handle: (3) }) Or, some options: attach(“eth0”, TcAttachType::Ingress, TcOptions { priority: (50), ..Default::default() }) Signed-off-by: Andre Fredette --- aya/src/programs/tc.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs index 7748a845..a6c65062 100644 --- a/aya/src/programs/tc.rs +++ b/aya/src/programs/tc.rs @@ -55,6 +55,7 @@ pub enum TcAttachType { /// # Bpf(#[from] aya::BpfError) /// # } /// # let mut bpf = aya::Bpf::load(&[])?; +/// use aya::programs::tc::TcOptions; /// use aya::programs::{tc, SchedClassifier, TcAttachType}; /// /// // the clsact qdisc needs to be added before SchedClassifier programs can be @@ -63,7 +64,7 @@ pub enum TcAttachType { /// /// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?; /// prog.load()?; -/// prog.attach("eth0", TcAttachType::Ingress, 0, 0)?; +/// prog.attach("eth0", TcAttachType::Ingress, TcOptions::default())?; /// /// # Ok::<(), Error>(()) /// ``` @@ -99,6 +100,17 @@ impl TcAttachType { } } +/// Options for SchedClassifier attach +#[derive(Default)] +pub struct TcOptions { + /// `priority`: priority assigned to tc program with lower number = higher priority. + /// If set to default (0), the system chooses the next highest priority or 49152 if no filters exist yet + pub priority: u16, + /// `handle`: used to uniquely identify a program at a given priority level. + /// If set to default (0), the system chooses a handle. + pub handle: u32, +} + impl SchedClassifier { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { @@ -107,13 +119,6 @@ impl SchedClassifier { /// Attaches the program to the given `interface`. /// - /// Valid priority values range from 0 - 65535 with lower number = higher priority. - /// 0 means let the system choose the next highest priority, or 49152 if no filters exist yet. - /// All other values in the range are taken as an explicit priority setting (aka "preference"). - /// - /// `handle` is used to uniquely identify a program at a given priority level. - /// If set to 0, the system will choose a handle. - /// /// The returned value can be used to detach, see [SchedClassifier::detach]. /// /// # Errors @@ -126,8 +131,7 @@ impl SchedClassifier { &mut self, interface: &str, attach_type: TcAttachType, - priority: u16, - handle: u32, + options: TcOptions, ) -> Result { let prog_fd = self.data.fd_or_err()?; let if_index = ifindex_from_ifname(interface) @@ -138,8 +142,8 @@ impl SchedClassifier { &attach_type, prog_fd, &self.name, - priority, - handle, + options.priority, + options.handle, ) } .map_err(|io_error| TcError::NetlinkError { io_error })?; From a3e3e806986b6c3761b1072626825d7f58376c50 Mon Sep 17 00:00:00 2001 From: Andre Fredette Date: Wed, 9 Nov 2022 13:22:15 -0500 Subject: [PATCH 3/4] Support both attach() and attach_with_options() for SchedClassifier Signed-off-by: Andre Fredette --- aya/src/programs/tc.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs index a6c65062..06299325 100644 --- a/aya/src/programs/tc.rs +++ b/aya/src/programs/tc.rs @@ -64,7 +64,12 @@ pub enum TcAttachType { /// /// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?; /// prog.load()?; -/// prog.attach("eth0", TcAttachType::Ingress, TcOptions::default())?; +/// +/// // the following demonstrates using the standard attach with default options +/// prog.attach("eth0", TcAttachType::Ingress)?; +/// +/// // the following demonstrates the `attach_with_options` variant +/// prog.attach_with_options("eth0", TcAttachType::Ingress, TcOptions {priority: 50, handle: 3})?; /// /// # Ok::<(), Error>(()) /// ``` @@ -117,7 +122,7 @@ impl SchedClassifier { load_program(BPF_PROG_TYPE_SCHED_CLS, &mut self.data) } - /// Attaches the program to the given `interface`. + /// Attaches the program to the given `interface` using the default options. /// /// The returned value can be used to detach, see [SchedClassifier::detach]. /// @@ -131,6 +136,24 @@ impl SchedClassifier { &mut self, interface: &str, attach_type: TcAttachType, + ) -> Result { + self.attach_with_options(interface, attach_type, TcOptions::default()) + } + + /// Attaches the program to the given `interface` with options defined in [`TcOptions`]. + /// + /// The returned value can be used to detach, see [SchedClassifier::detach]. + /// + /// # 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`] + /// + pub fn attach_with_options( + &mut self, + interface: &str, + attach_type: TcAttachType, options: TcOptions, ) -> Result { let prog_fd = self.data.fd_or_err()?; From abb75ba0293ca7b68abeff3d670dadc5997eb959 Mon Sep 17 00:00:00 2001 From: Andre Fredette Date: Thu, 17 Nov 2022 17:38:48 -0500 Subject: [PATCH 4/4] Make doc fixes Signed-off-by: Andre Fredette --- aya/src/programs/tc.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs index 06299325..9b14858d 100644 --- a/aya/src/programs/tc.rs +++ b/aya/src/programs/tc.rs @@ -55,7 +55,6 @@ pub enum TcAttachType { /// # Bpf(#[from] aya::BpfError) /// # } /// # let mut bpf = aya::Bpf::load(&[])?; -/// use aya::programs::tc::TcOptions; /// use aya::programs::{tc, SchedClassifier, TcAttachType}; /// /// // the clsact qdisc needs to be added before SchedClassifier programs can be @@ -64,13 +63,8 @@ pub enum TcAttachType { /// /// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?; /// prog.load()?; -/// -/// // the following demonstrates using the standard attach with default options /// prog.attach("eth0", TcAttachType::Ingress)?; /// -/// // the following demonstrates the `attach_with_options` variant -/// prog.attach_with_options("eth0", TcAttachType::Ingress, TcOptions {priority: 50, handle: 3})?; -/// /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] @@ -108,10 +102,10 @@ impl TcAttachType { /// Options for SchedClassifier attach #[derive(Default)] pub struct TcOptions { - /// `priority`: priority assigned to tc program with lower number = higher priority. - /// If set to default (0), the system chooses the next highest priority or 49152 if no filters exist yet + /// Priority assigned to tc program with lower number = higher priority. + /// If set to default (0), the system chooses the next highest priority or 49152 if no filters exist yet pub priority: u16, - /// `handle`: used to uniquely identify a program at a given priority level. + /// Handle used to uniquely identify a program at a given priority level. /// If set to default (0), the system chooses a handle. pub handle: u32, }