use thiserror::Error;
use std::{
ffi::{CStr, CString},
io,
};
use crate::{
generated::{
bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS, TC_H_CLSACT, TC_H_MIN_EGRESS, TC_H_MIN_INGRESS,
},
programs::{define_link_wrapper, load_program, Link, OwnedLink, ProgramData, ProgramError},
sys::{
netlink_find_filter_with_name, netlink_qdisc_add_clsact, netlink_qdisc_attach,
netlink_qdisc_detach,
},
util::{ifindex_from_ifname, tc_handler_make},
};
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub enum TcAttachType {
Ingress,
Egress,
Custom(u32),
}
#[derive(Debug)]
#[doc(alias = "BPF_PROG_TYPE_SCHED_CLS")]
pub struct SchedClassifier {
pub(crate) data: ProgramData<SchedClassifierLink>,
pub(crate) name: Box<CStr>,
}
#[derive(Debug, Error)]
pub enum TcError {
#[error("netlink error while attaching ebpf program to tc")]
NetlinkError {
#[source]
io_error: io::Error,
},
#[error("the clsact qdisc is already attached")]
AlreadyAttached,
}
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),
}
}
}
impl SchedClassifier {
pub fn load(&mut self) -> Result<(), ProgramError> {
load_program(BPF_PROG_TYPE_SCHED_CLS, &mut self.data)
}
pub fn attach(
&mut self,
interface: &str,
attach_type: TcAttachType,
) -> Result<SchedClassifierLinkId, ProgramError> {
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) }
.map_err(|io_error| TcError::NetlinkError { io_error })?;
self.data.links.insert(SchedClassifierLink(TcLink {
if_index: if_index as i32,
attach_type,
priority,
}))
}
pub fn detach(&mut self, link_id: SchedClassifierLinkId) -> Result<(), ProgramError> {
self.data.links.remove(link_id)
}
pub fn take_link(
&mut self,
link_id: SchedClassifierLinkId,
) -> Result<OwnedLink<SchedClassifierLink>, ProgramError> {
Ok(OwnedLink::new(self.data.take_link(link_id)?))
}
}
#[derive(Debug, Hash, Eq, PartialEq)]
pub(crate) struct TcLinkId(i32, TcAttachType, u32);
#[derive(Debug)]
struct TcLink {
if_index: i32,
attach_type: TcAttachType,
priority: u32,
}
impl Link for TcLink {
type Id = TcLinkId;
fn id(&self) -> Self::Id {
TcLinkId(self.if_index, self.attach_type, self.priority)
}
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 })?;
Ok(())
}
}
define_link_wrapper!(
SchedClassifierLink,
SchedClassifierLinkId,
TcLink,
TcLinkId
);
pub fn qdisc_add_clsact(if_name: &str) -> Result<(), io::Error> {
let if_index = ifindex_from_ifname(if_name)?;
unsafe { netlink_qdisc_add_clsact(if_index as i32) }
}
pub fn qdisc_detach_program(
if_name: &str,
attach_type: TcAttachType,
name: &str,
) -> Result<(), io::Error> {
let cstr = CString::new(name)?;
qdisc_detach_program_fast(if_name, attach_type, &cstr)
}
fn qdisc_detach_program_fast(
if_name: &str,
attach_type: TcAttachType,
name: &CStr,
) -> 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() {
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)? };
}
Ok(())
}