Merge pull request #418 from anfredette/tc-handle

Support using handle in tc programs
pull/446/head
Alessandro Decina 2 years ago committed by GitHub
commit 7fef833e3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
.gitignore vendored

@ -5,3 +5,4 @@ libbpf/
!.vscode/settings.json !.vscode/settings.json
site/ site/
header.html header.html
.idea/

@ -63,7 +63,7 @@ pub enum TcAttachType {
/// ///
/// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?; /// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?;
/// prog.load()?; /// prog.load()?;
/// prog.attach("eth0", TcAttachType::Ingress, 0)?; /// prog.attach("eth0", TcAttachType::Ingress)?;
/// ///
/// # Ok::<(), Error>(()) /// # Ok::<(), Error>(())
/// ``` /// ```
@ -99,17 +99,24 @@ impl TcAttachType {
} }
} }
/// Options for SchedClassifier attach
#[derive(Default)]
pub struct TcOptions {
/// 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 { impl SchedClassifier {
/// Loads the program inside the kernel. /// Loads the program inside the kernel.
pub fn load(&mut self) -> Result<(), ProgramError> { pub fn load(&mut self) -> Result<(), ProgramError> {
load_program(BPF_PROG_TYPE_SCHED_CLS, &mut self.data) 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.
///
/// 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").
/// ///
/// The returned value can be used to detach, see [SchedClassifier::detach]. /// The returned value can be used to detach, see [SchedClassifier::detach].
/// ///
@ -123,13 +130,38 @@ impl SchedClassifier {
&mut self, &mut self,
interface: &str, interface: &str,
attach_type: TcAttachType, attach_type: TcAttachType,
priority: u16, ) -> Result<SchedClassifierLinkId, ProgramError> {
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<SchedClassifierLinkId, ProgramError> { ) -> Result<SchedClassifierLinkId, ProgramError> {
let prog_fd = self.data.fd_or_err()?; let prog_fd = self.data.fd_or_err()?;
let if_index = ifindex_from_ifname(interface) let if_index = ifindex_from_ifname(interface)
.map_err(|io_error| TcError::NetlinkError { io_error })?; .map_err(|io_error| TcError::NetlinkError { io_error })?;
let priority = unsafe { let (priority, handle) = unsafe {
netlink_qdisc_attach(if_index as i32, &attach_type, prog_fd, &self.name, priority) netlink_qdisc_attach(
if_index as i32,
&attach_type,
prog_fd,
&self.name,
options.priority,
options.handle,
)
} }
.map_err(|io_error| TcError::NetlinkError { io_error })?; .map_err(|io_error| TcError::NetlinkError { io_error })?;
@ -137,6 +169,7 @@ impl SchedClassifier {
if_index: if_index as i32, if_index: if_index as i32,
attach_type, attach_type,
priority, priority,
handle,
})) }))
} }
@ -160,25 +193,28 @@ impl SchedClassifier {
} }
#[derive(Debug, Hash, Eq, PartialEq)] #[derive(Debug, Hash, Eq, PartialEq)]
pub(crate) struct TcLinkId(i32, TcAttachType, u16); pub(crate) struct TcLinkId(i32, TcAttachType, u16, u32);
#[derive(Debug)] #[derive(Debug)]
struct TcLink { struct TcLink {
if_index: i32, if_index: i32,
attach_type: TcAttachType, attach_type: TcAttachType,
priority: u16, priority: u16,
handle: u32,
} }
impl Link for TcLink { impl Link for TcLink {
type Id = TcLinkId; type Id = TcLinkId;
fn id(&self) -> Self::Id { 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> { fn detach(self) -> Result<(), ProgramError> {
unsafe { netlink_qdisc_detach(self.if_index, &self.attach_type, self.priority) } unsafe {
.map_err(|io_error| TcError::NetlinkError { io_error })?; netlink_qdisc_detach(self.if_index, &self.attach_type, self.priority, self.handle)
}
.map_err(|io_error| TcError::NetlinkError { io_error })?;
Ok(()) Ok(())
} }
} }
@ -233,16 +269,16 @@ fn qdisc_detach_program_fast(
) -> Result<(), io::Error> { ) -> Result<(), io::Error> {
let if_index = ifindex_from_ifname(if_name)? as i32; let if_index = ifindex_from_ifname(if_name)? as i32;
let prios = unsafe { netlink_find_filter_with_name(if_index, attach_type, name)? }; let filter_info = unsafe { netlink_find_filter_with_name(if_index, attach_type, name)? };
if prios.is_empty() { if filter_info.is_empty() {
return Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::NotFound, io::ErrorKind::NotFound,
name.to_string_lossy(), name.to_string_lossy(),
)); ));
} }
for prio in prios { for (prio, handle) in filter_info {
unsafe { netlink_qdisc_detach(if_index, &attach_type, prio)? }; unsafe { netlink_qdisc_detach(if_index, &attach_type, prio, handle)? };
} }
Ok(()) Ok(())

@ -104,7 +104,8 @@ pub(crate) unsafe fn netlink_qdisc_attach(
prog_fd: RawFd, prog_fd: RawFd,
prog_name: &CStr, prog_name: &CStr,
priority: u16, priority: u16,
) -> Result<u16, io::Error> { handle: u32,
) -> Result<(u16, u32), io::Error> {
let sock = NetlinkSocket::open()?; let sock = NetlinkSocket::open()?;
let mut req = mem::zeroed::<TcRequest>(); let mut req = mem::zeroed::<TcRequest>();
@ -117,7 +118,7 @@ pub(crate) unsafe fn netlink_qdisc_attach(
nlmsg_seq: 1, nlmsg_seq: 1,
}; };
req.tc_info.tcm_family = AF_UNSPEC as u8; 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_ifindex = if_index;
req.tc_info.tcm_parent = attach_type.parent(); 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); 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; 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])?; 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 // find the RTM_NEWTFILTER reply and read the tcm_info and tcm_handle fields
// need to detach // which we'll need to detach
let tc_info = match sock let tc_msg = match sock
.recv()? .recv()?
.iter() .iter()
.find(|reply| reply.header.nlmsg_type == RTM_NEWTFILTER) .find(|reply| reply.header.nlmsg_type == RTM_NEWTFILTER)
{ {
Some(reply) => { Some(reply) => ptr::read_unaligned(reply.data.as_ptr() as *const tcmsg),
let msg = ptr::read_unaligned(reply.data.as_ptr() as *const tcmsg);
msg.tcm_info
}
None => { None => {
// if sock.recv() succeeds we should never get here unless there's a // if sock.recv() succeeds we should never get here unless there's a
// bug in the kernel // 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; let priority = ((tc_msg.tcm_info & TC_H_MAJ_MASK) >> 16) as u16;
Ok(priority) Ok((priority, tc_msg.tcm_handle))
} }
pub(crate) unsafe fn netlink_qdisc_detach( pub(crate) unsafe fn netlink_qdisc_detach(
if_index: i32, if_index: i32,
attach_type: &TcAttachType, attach_type: &TcAttachType,
priority: u16, priority: u16,
handle: u32,
) -> Result<(), io::Error> { ) -> Result<(), io::Error> {
let sock = NetlinkSocket::open()?; let sock = NetlinkSocket::open()?;
let mut req = mem::zeroed::<TcRequest>(); let mut req = mem::zeroed::<TcRequest>();
@ -180,7 +179,7 @@ pub(crate) unsafe fn netlink_qdisc_detach(
}; };
req.tc_info.tcm_family = AF_UNSPEC as u8; 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_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_parent = attach_type.parent();
req.tc_info.tcm_ifindex = if_index; req.tc_info.tcm_ifindex = if_index;
@ -192,11 +191,12 @@ pub(crate) unsafe fn netlink_qdisc_detach(
Ok(()) Ok(())
} }
// Returns a vector of tuple (priority, handle) for filters matching the provided parameters
pub(crate) unsafe fn netlink_find_filter_with_name( pub(crate) unsafe fn netlink_find_filter_with_name(
if_index: i32, if_index: i32,
attach_type: TcAttachType, attach_type: TcAttachType,
name: &CStr, name: &CStr,
) -> Result<Vec<u16>, io::Error> { ) -> Result<Vec<(u16, u32)>, io::Error> {
let mut req = mem::zeroed::<TcRequest>(); let mut req = mem::zeroed::<TcRequest>();
let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<tcmsg>(); let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<tcmsg>();
@ -208,14 +208,14 @@ pub(crate) unsafe fn netlink_find_filter_with_name(
nlmsg_seq: 1, nlmsg_seq: 1,
}; };
req.tc_info.tcm_family = AF_UNSPEC as u8; 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_ifindex = if_index;
req.tc_info.tcm_parent = attach_type.parent(); req.tc_info.tcm_parent = attach_type.parent();
let sock = NetlinkSocket::open()?; let sock = NetlinkSocket::open()?;
sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; 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()? { for msg in sock.recv()? {
if msg.header.nlmsg_type != RTM_NEWTFILTER { if msg.header.nlmsg_type != RTM_NEWTFILTER {
continue; 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 Some(f_name) = opts.get(&(TCA_BPF_NAME as u16)) {
if let Ok(f_name) = CStr::from_bytes_with_nul(f_name.data) { if let Ok(f_name) = CStr::from_bytes_with_nul(f_name.data) {
if name == f_name { if name == f_name {
prios.push(priority); filter_info.push((priority, tc_msg.tcm_handle));
} }
} }
} }
} }
} }
Ok(prios) Ok(filter_info)
} }
#[repr(C)] #[repr(C)]

Loading…
Cancel
Save