use rtnetlink::packet_core::{
NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REPLACE, NLM_F_REQUEST, NetlinkMessage,
};
use rtnetlink::packet_route::{
RouteNetlinkMessage,
tc::{TcAttribute, TcMessage},
};
use super::core::{MTU_ETHERNET, TICK_IN_USEC};
use super::handle::QdiscRequestInner;
use super::impairment::LinkImpairment;
use super::nla::{build_nested_options, build_nla};
const DEFAULT_RATE_TABLE: [u8; 1024] = [0u8; 1024];
const TCA_TBF_PARMS: u16 = 1;
const TCA_TBF_RTAB: u16 = 2;
const TCA_TBF_BURST: u16 = 6;
#[derive(Debug, Clone, Copy)]
pub struct TbfQopt {
pub rate: TcRateSpec,
pub peakrate: TcRateSpec,
pub limit: u32,
pub buffer: u32,
pub mtu: u32,
pub burst_bytes: u32,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct TcRateSpec {
pub cell_log: u8,
pub linklayer: u8,
pub overhead: u16,
pub cell_align: i16,
pub mpu: u16,
pub rate: u32,
}
impl TcRateSpec {
pub fn new(rate_bytes_per_sec: u32, mtu: u32) -> Self {
let cell_log = Self::compute_cell_log(mtu);
Self {
rate: rate_bytes_per_sec,
linklayer: 0,
cell_log,
cell_align: -1, ..Default::default()
}
}
pub fn compute_cell_log(mtu: u32) -> u8 {
let mut cell_log = 0u8;
while (mtu >> cell_log) > 255 {
cell_log += 1;
}
cell_log
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut vec = Vec::with_capacity(12);
vec.push(self.cell_log);
vec.push(self.linklayer);
vec.extend_from_slice(&self.overhead.to_ne_bytes());
vec.extend_from_slice(&self.cell_align.to_ne_bytes());
vec.extend_from_slice(&self.mpu.to_ne_bytes());
vec.extend_from_slice(&self.rate.to_ne_bytes());
vec
}
}
impl TbfQopt {
pub fn to_bytes(&self) -> Vec<u8> {
let mut vec = Vec::with_capacity(12 + 12 + 4 + 4 + 4); vec.extend_from_slice(&self.rate.to_bytes());
vec.extend_from_slice(&self.peakrate.to_bytes());
vec.extend_from_slice(&self.limit.to_ne_bytes());
vec.extend_from_slice(&self.buffer.to_ne_bytes());
vec.extend_from_slice(&self.mtu.to_ne_bytes());
vec
}
pub fn try_from_impairment(impairment: &LinkImpairment) -> Option<Self> {
let bandwidth_mbit = impairment.bandwidth_mbit_s?;
let rate_bytes_per_sec = (bandwidth_mbit * 1_000_000.0 / 8.0) as u32;
let burst_bytes = impairment.effective_burst_bytes();
let tick_in_usec = *TICK_IN_USEC;
let buffer_ticks =
(burst_bytes as f64 * tick_in_usec * 1_000_000.0 / rate_bytes_per_sec as f64) as u32;
let limit_bytes = impairment.effective_tbf_limit_bytes();
Some(Self {
rate: TcRateSpec::new(rate_bytes_per_sec, MTU_ETHERNET),
peakrate: TcRateSpec::default(), limit: limit_bytes,
buffer: buffer_ticks,
mtu: MTU_ETHERNET,
burst_bytes,
})
}
}
#[derive(Debug, Clone)]
pub struct QdiscTbfRequest {
pub inner: QdiscRequestInner,
pub options: TbfQopt,
pub replace: bool,
}
impl QdiscTbfRequest {
pub fn try_new(inner: QdiscRequestInner, impairment: &LinkImpairment) -> Option<Self> {
Some(Self { inner, options: TbfQopt::try_from_impairment(impairment)?, replace: false })
}
pub fn with_replace(mut self, replace: bool) -> Self {
self.replace = replace;
self
}
pub fn build(self) -> NetlinkMessage<RouteNetlinkMessage> {
let mut tc_message = TcMessage::with_index(self.inner.interface_index);
tc_message.header.parent = self.inner.parent;
tc_message.header.handle = self.inner.handle;
tc_message.attributes.push(TcAttribute::Kind("tbf".to_string()));
let tbf_parms_nla = build_nla(TCA_TBF_PARMS, &self.options.to_bytes());
let tbf_rtab_nla = build_nla(TCA_TBF_RTAB, &DEFAULT_RATE_TABLE);
let tbf_burst_nla = build_nla(TCA_TBF_BURST, &self.options.burst_bytes.to_ne_bytes());
let mut combined_nlas = tbf_parms_nla;
combined_nlas.extend(tbf_rtab_nla);
combined_nlas.extend(tbf_burst_nla);
tc_message.attributes.push(TcAttribute::Other(build_nested_options(combined_nlas)));
let mut nl_req = NetlinkMessage::from(RouteNetlinkMessage::NewQueueDiscipline(tc_message));
nl_req.header.flags = if self.replace {
NLM_F_CREATE | NLM_F_REPLACE | NLM_F_REQUEST | NLM_F_ACK
} else {
NLM_F_CREATE | NLM_F_EXCL | NLM_F_REQUEST | NLM_F_ACK
};
tracing::debug!(?nl_req, "sending tbf request");
nl_req
}
}