aya: refactor tc code a bit and add docs

pull/1/head
Alessandro Decina 3 years ago
parent 11e21e83be
commit ad58e171ff

@ -44,7 +44,7 @@ mod sk_msg;
mod sk_skb;
mod sock_ops;
mod socket_filter;
mod tc;
pub mod tc;
mod trace_point;
mod uprobe;
mod xdp;
@ -61,7 +61,7 @@ pub use sk_msg::SkMsg;
pub use sk_skb::{SkSkb, SkSkbKind};
pub use sock_ops::SockOps;
pub use socket_filter::{SocketFilter, SocketFilterError};
pub use tc::{SchedClassifier, TcAttachPoint, TcError};
pub use tc::{SchedClassifier, TcAttachType, TcError};
pub use trace_point::{TracePoint, TracePointError};
pub use uprobe::{UProbe, UProbeError};
pub use xdp::{Xdp, XdpError, XdpFlags};

@ -1,25 +1,63 @@
//! Network traffic control programs.
use thiserror::Error;
use std::{io, os::unix::io::RawFd};
use crate::{
generated::{
TC_H_CLSACT, TC_H_MIN_INGRESS, TC_H_MIN_EGRESS,
bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS,
bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS, TC_H_CLSACT, TC_H_MIN_EGRESS, TC_H_MIN_INGRESS,
},
programs::{Link, LinkRef, load_program, ProgramData, ProgramError},
programs::{load_program, Link, LinkRef, ProgramData, ProgramError},
sys::{netlink_qdisc_add_clsact, netlink_qdisc_attach, netlink_qdisc_detach},
util::{ifindex_from_ifname, tc_handler_make},
};
/// Traffic control attach type.
#[derive(Debug, Clone, Copy)]
#[repr(u32)]
pub enum TcAttachPoint {
Ingress = TC_H_MIN_INGRESS,
Egress = TC_H_MIN_EGRESS,
Custom,
pub enum TcAttachType {
/// Attach to ingress.
Ingress,
/// Attach to egress.
Egress,
/// Attach to custom parent.
Custom(u32),
}
/// A network traffic control classifier.
///
/// [`SchedClassifier`] programs can be used to inspect, filter or redirect
/// network packets in both ingress and egress. They are executed as part of the
/// linux network traffic control system. See
/// [https://man7.org/linux/man-pages/man8/tc-bpf.8.html](https://man7.org/linux/man-pages/man8/tc-bpf.8.html).
///
/// # Example
///
/// ```no_run
/// ##[derive(Debug, thiserror::Error)]
/// # enum Error {
/// # #[error(transparent)]
/// # IO(#[from] std::io::Error),
/// # #[error(transparent)]
/// # Map(#[from] aya::maps::MapError),
/// # #[error(transparent)]
/// # Program(#[from] aya::programs::ProgramError),
/// # #[error(transparent)]
/// # Bpf(#[from] aya::BpfError)
/// # }
/// # let mut bpf = aya::Bpf::load(&[], None)?;
/// use std::convert::TryInto;
/// use aya::programs::{tc, SchedClassifier, TcAttachType};
///
/// // the clsact qdisc needs to be added before SchedClassifier programs can be
/// // attached
/// tc::qdisc_add_clsact("eth0")?;
///
/// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress")?.try_into()?;
/// prog.load()?;
/// prog.attach("eth0", TcAttachType::Ingress)?;
///
/// # Ok::<(), Error>(())
/// ```
#[derive(Debug)]
pub struct SchedClassifier {
pub(crate) data: ProgramData,
@ -39,21 +77,17 @@ pub enum TcError {
#[derive(Debug)]
struct TcLink {
if_index: i32,
attach_point: TcAttachPoint,
attach_type: TcAttachType,
prog_fd: Option<RawFd>,
priority: u32,
}
impl TcAttachPoint {
pub fn tcm_parent(&self, parent: u32) -> Result<u32, io::Error> {
match *self {
TcAttachPoint::Custom => {
if parent == 0 {
return Err(io::Error::new(io::ErrorKind::Other, "Parent must be non-zero for Custom attach points"));
}
Ok(parent)
}
_ => Ok(tc_handler_make(TC_H_CLSACT, (*self).clone() as u32))
impl TcAttachType {
pub(crate) fn parent(&self) -> u32 {
match self {
TcAttachType::Custom(parent) => *parent,
TcAttachType::Ingress => tc_handler_make(TC_H_CLSACT, TC_H_MIN_INGRESS),
TcAttachType::Egress => tc_handler_make(TC_H_CLSACT, TC_H_MIN_EGRESS),
}
}
}
@ -71,31 +105,33 @@ impl SchedClassifier {
self.data.name.to_string()
}
/// Attaches the program to the given `interface` and `attach-point`
pub fn attach(&mut self, interface: &str, attach_point: TcAttachPoint) -> Result<LinkRef, ProgramError> {
/// Attaches the program to the given `interface`.
///
/// # 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(
&mut self,
interface: &str,
attach_type: TcAttachType,
) -> Result<LinkRef, ProgramError> {
let prog_fd = self.data.fd_or_err()?;
let if_index = unsafe { ifindex_from_ifname(interface) }
.map_err(|io_error| TcError::NetlinkError { io_error })?;
let prog_name = self.name();
let priority = unsafe { netlink_qdisc_attach(if_index as i32, &attach_point, prog_fd, &prog_name[..]) }
let if_index = ifindex_from_ifname(interface)
.map_err(|io_error| TcError::NetlinkError { io_error })?;
let prog_name = self.name();
let priority =
unsafe { netlink_qdisc_attach(if_index as i32, &attach_type, prog_fd, &prog_name[..]) }
.map_err(|io_error| TcError::NetlinkError { io_error })?;
Ok(self.data.link(TcLink {
if_index: if_index as i32,
attach_point,
attach_type,
prog_fd: Some(prog_fd),
priority,
}))
}
/// Add "clasct" qdisc to an interface
pub fn qdisc_add_clsact_to_interface(if_name: &str) -> Result<(), ProgramError> {
// unsafe wrapper
let if_index = unsafe { ifindex_from_ifname(if_name) }
.map_err(|_| ProgramError::UnknownInterface {name: if_name.to_string()})?;
unsafe { netlink_qdisc_add_clsact(if_index as i32) }
.map_err(|io_error| TcError::NetlinkError { io_error })?;
Ok(())
}
}
impl Drop for TcLink {
@ -107,7 +143,7 @@ impl Drop for TcLink {
impl Link for TcLink {
fn detach(&mut self) -> Result<(), ProgramError> {
if let Some(_) = self.prog_fd.take() {
unsafe { netlink_qdisc_detach(self.if_index, &self.attach_point, self.priority) }
unsafe { netlink_qdisc_detach(self.if_index, &self.attach_type, self.priority) }
.map_err(|io_error| TcError::NetlinkError { io_error })?;
Ok(())
} else {
@ -116,3 +152,15 @@ impl Link for TcLink {
}
}
/// Add the `clasct` qdisc to the given interface.
///
/// The `clsact` qdisc must be added to an interface before [`SchedClassifier`]
/// programs can be attached.
pub fn qdisc_add_clsact(if_name: &str) -> Result<(), ProgramError> {
let if_index = ifindex_from_ifname(if_name).map_err(|_| ProgramError::UnknownInterface {
name: if_name.to_string(),
})?;
unsafe { netlink_qdisc_add_clsact(if_index as i32) }
.map_err(|io_error| TcError::NetlinkError { io_error })?;
Ok(())
}

@ -1,28 +1,23 @@
use std::{
io,
mem,
os::unix::io::RawFd,
ptr,
slice,
};
use std::{io, mem, os::unix::io::RawFd, ptr, slice};
use libc::{
c_int, close, getsockname, nlattr, nlmsgerr, nlmsghdr, recv, send, setsockopt, sockaddr_nl,
socket, AF_NETLINK, AF_UNSPEC, IFLA_XDP, NETLINK_ROUTE, NLA_ALIGNTO, NLA_F_NESTED, NLMSG_DONE,
NLMSG_ERROR, NLM_F_ACK, NLM_F_EXCL, NLM_F_ECHO, NLM_F_CREATE, NLM_F_MULTI, NLM_F_REQUEST, RTM_SETLINK, SOCK_RAW,
SOL_NETLINK, RTM_NEWQDISC, RTM_NEWTFILTER, RTM_DELTFILTER, ETH_P_ALL,
socket, AF_NETLINK, AF_UNSPEC, ETH_P_ALL, IFLA_XDP, NETLINK_ROUTE, NLA_ALIGNTO, NLA_F_NESTED,
NLMSG_DONE, NLMSG_ERROR, NLM_F_ACK, NLM_F_CREATE, NLM_F_ECHO, NLM_F_EXCL, NLM_F_MULTI,
NLM_F_REQUEST, RTM_DELTFILTER, RTM_NEWQDISC, RTM_NEWTFILTER, RTM_SETLINK, SOCK_RAW,
SOL_NETLINK,
};
use crate::{
generated::{
_bindgen_ty_79::{IFLA_XDP_EXPECTED_FD, IFLA_XDP_FD, IFLA_XDP_FLAGS},
_bindgen_ty_91::{TCA_KIND, TCA_OPTIONS},
_bindgen_ty_133::{TCA_BPF_FD, TCA_BPF_NAME, TCA_BPF_FLAGS},
ifinfomsg, tcmsg, NLMSG_ALIGNTO, XDP_FLAGS_REPLACE,
TC_H_UNSPEC, TCA_BPF_FLAG_ACT_DIRECT, TC_H_CLSACT, TC_H_INGRESS, TC_H_MAJ_MASK,
_bindgen_ty_133::{TCA_BPF_FD, TCA_BPF_FLAGS, TCA_BPF_NAME},
_bindgen_ty_79::{IFLA_XDP_EXPECTED_FD, IFLA_XDP_FD, IFLA_XDP_FLAGS},
_bindgen_ty_91::{TCA_KIND, TCA_OPTIONS},
ifinfomsg, tcmsg, NLMSG_ALIGNTO, TCA_BPF_FLAG_ACT_DIRECT, TC_H_CLSACT, TC_H_INGRESS,
TC_H_MAJ_MASK, TC_H_UNSPEC, XDP_FLAGS_REPLACE,
},
util::{htons, tc_handler_make},
programs::TcAttachPoint,
programs::TcAttachType,
util::tc_handler_make,
};
const NETLINK_EXT_ACK: c_int = 11;
@ -130,9 +125,7 @@ pub(crate) unsafe fn netlink_set_xdp_fd(
Ok(())
}
pub(crate) unsafe fn netlink_qdisc_add_clsact(
if_index: i32
) -> Result<(), io::Error> {
pub(crate) unsafe fn netlink_qdisc_add_clsact(if_index: i32) -> Result<(), io::Error> {
let sock = NetlinkSocket::open()?;
let seq = 1;
@ -191,14 +184,14 @@ pub(crate) unsafe fn netlink_qdisc_add_clsact(
pub(crate) unsafe fn netlink_qdisc_detach(
if_index: i32,
attach_point: &TcAttachPoint,
attach_type: &TcAttachType,
priority: u32,
) -> Result<(), io::Error> {
let sock = NetlinkSocket::open()?;
let sock = NetlinkSocket::open()?;
let seq = 1;
let mut req = mem::zeroed::<QdiscRequest>();
req.header = nlmsghdr{
req.header = nlmsghdr {
nlmsg_len: (mem::size_of::<nlmsghdr>() + mem::size_of::<tcmsg>()) as u32,
nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) as u16,
nlmsg_type: RTM_DELTFILTER,
@ -209,7 +202,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_info = tc_handler_make(priority << 16, htons(ETH_P_ALL as u16) as u32);
req.tc_info.tcm_parent = attach_point.tcm_parent(0)?;
req.tc_info.tcm_parent = attach_type.parent();
req.tc_info.tcm_ifindex = if_index;
if send(
@ -229,7 +222,7 @@ pub(crate) unsafe fn netlink_qdisc_detach(
pub(crate) unsafe fn netlink_qdisc_attach(
if_index: i32,
attach_point: &TcAttachPoint,
attach_type: &TcAttachType,
prog_fd: RawFd,
prog_name: &str,
) -> Result<u32, io::Error> {
@ -238,7 +231,7 @@ pub(crate) unsafe fn netlink_qdisc_attach(
let priority = 0;
let mut req = mem::zeroed::<QdiscRequest>();
req.header = nlmsghdr{
req.header = nlmsghdr {
nlmsg_len: (mem::size_of::<nlmsghdr>() + mem::size_of::<tcmsg>()) as u32,
nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE | NLM_F_ECHO) as u16,
nlmsg_type: RTM_NEWTFILTER,
@ -246,9 +239,9 @@ pub(crate) unsafe fn netlink_qdisc_attach(
nlmsg_seq: seq,
};
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 not provided
req.tc_info.tcm_ifindex = if_index;
req.tc_info.tcm_parent = attach_point.tcm_parent(0)?;
req.tc_info.tcm_parent = attach_type.parent();
req.tc_info.tcm_info = tc_handler_make(priority << 16, htons(ETH_P_ALL as u16) as u32);
@ -279,10 +272,10 @@ pub(crate) unsafe fn netlink_qdisc_attach(
let nested_tca_options_start = nla_len;
let nested_attr_offset = offset;
// now write the nested portion
let mut nested_attr = nlattr {
nla_type: TCA_OPTIONS as u16 | NLA_F_NESTED as u16,
nla_len: nla_hdr_len as u16, // no data
nla_len: nla_hdr_len as u16, // no data
};
offset += nla_hdr_len;
@ -329,7 +322,7 @@ pub(crate) unsafe fn netlink_qdisc_attach(
ptr::write(offset as *mut u32, bpf_flags);
nla_len += attr.nla_len;
// now write the NESTED nlattr
// now write the NESTED nlattr
nested_attr.nla_len = nla_len - nested_tca_options_start;
ptr::write(nested_attr_offset as *mut nlattr, nested_attr);
req.header.nlmsg_len += align_to(nla_len as usize, NLA_ALIGNTO as usize) as u32;
@ -504,3 +497,6 @@ fn align_to(v: usize, align: usize) -> usize {
(v + (align - 1)) & !(align - 1)
}
fn htons(u: u16) -> u16 {
u.to_be()
}

@ -4,7 +4,6 @@ use std::{
ffi::CString,
fs::{self, File},
io::{self, BufReader},
os::raw::c_char,
str::FromStr,
};
@ -35,10 +34,6 @@ pub fn nr_cpus() -> Result<usize, io::Error> {
Ok(possible_cpus()?.len())
}
pub(crate) fn tc_handler_make(major: u32, minor: u32) -> u32 {
(major & TC_H_MAJ_MASK) | (minor & TC_H_MIN_MASK)
}
/// Get the list of possible cpus.
///
/// See `/sys/devices/system/cpu/possible`.
@ -73,27 +68,6 @@ fn parse_cpu_ranges(data: &str) -> Result<Vec<u32>, ()> {
Ok(cpus)
}
/// Gets interface index from interface name.
pub unsafe fn ifindex_from_ifname(if_name: &str) -> Result<u32, io::Error> {
let c_str_if_name = CString::new(if_name)?;
let c_if_name: *const c_char = c_str_if_name.as_ptr() as *const c_char;
// unsafe libc wrapper
let if_index = if_nametoindex(c_if_name);
if if_index == 0 {
return Err(io::Error::last_os_error());
}
Ok(if_index)
}
/// htons and ntohs util functions
pub fn htons(u: u16) -> u16 {
u.to_be()
}
pub fn ntohs(u: u16) -> u16 {
u16::from_be(u)
}
/// Loads kernel symbols from `/proc/kallsyms`.
///
/// The symbols can be passed to [`StackTrace::resolve`](crate::maps::stack_trace::StackTrace::resolve).
@ -117,6 +91,21 @@ fn parse_kernel_symbols(reader: &mut dyn BufRead) -> Result<BTreeMap<u64, String
Ok(syms)
}
pub(crate) fn ifindex_from_ifname(if_name: &str) -> Result<u32, io::Error> {
let c_str_if_name = CString::new(if_name)?;
let c_if_name = c_str_if_name.as_ptr();
// Safety: libc wrapper
let if_index = unsafe { if_nametoindex(c_if_name) };
if if_index == 0 {
return Err(io::Error::last_os_error());
}
Ok(if_index)
}
pub(crate) fn tc_handler_make(major: u32, minor: u32) -> u32 {
(major & TC_H_MAJ_MASK) | (minor & TC_H_MIN_MASK)
}
#[cfg(test)]
mod tests {
use std::iter::FromIterator;

Loading…
Cancel
Save