use nix::libc::TCA_OPTIONS;
use rtnetlink::packet_core::{DefaultNla, NetlinkMessage, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REPLACE, NLM_F_REQUEST};
use rtnetlink::packet_route::{RouteNetlinkMessage, tc::{TcAttribute, TcMessage}};
use super::core::usec_to_ticks;
use super::handle::QdiscRequestInner;
use super::impairment::LinkImpairment;
#[derive(Debug, Clone, Copy)]
pub struct NetemQopt {
pub latency: u32,
pub limit: u32,
pub loss: u32,
pub gap: u32,
pub duplicate: u32,
pub jitter: u32,
}
impl NetemQopt {
pub fn u32_probability(percent: f64) -> u32 {
(percent / 100.0 * u32::MAX as f64) as u32
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut vec = Vec::with_capacity(4 * 6);
vec.extend_from_slice(&self.latency.to_ne_bytes());
vec.extend_from_slice(&self.limit.to_ne_bytes());
vec.extend_from_slice(&self.loss.to_ne_bytes());
vec.extend_from_slice(&self.gap.to_ne_bytes());
vec.extend_from_slice(&self.duplicate.to_ne_bytes());
vec.extend_from_slice(&self.jitter.to_ne_bytes());
vec
}
}
impl From<&LinkImpairment> for NetemQopt {
fn from(value: &LinkImpairment) -> Self {
Self {
latency: usec_to_ticks(value.latency),
limit: value.netem_limit,
loss: Self::u32_probability(value.loss),
gap: value.gap,
duplicate: Self::u32_probability(value.duplicate),
jitter: usec_to_ticks(value.jitter),
}
}
}
impl From<LinkImpairment> for NetemQopt {
fn from(value: LinkImpairment) -> Self {
Self::from(&value)
}
}
#[derive(Debug)]
pub struct QdiscNetemRequest {
pub inner: QdiscRequestInner,
pub options: NetemQopt,
pub replace: bool,
}
impl QdiscNetemRequest {
pub fn new(inner: QdiscRequestInner, options: NetemQopt) -> Self {
Self { inner, options, replace: false }
}
pub fn from_impairment(inner: QdiscRequestInner, impairment: &LinkImpairment) -> Self {
Self { inner, options: NetemQopt::from(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("netem".to_string()));
tc_message
.attributes
.push(TcAttribute::Other(DefaultNla::new(TCA_OPTIONS, self.options.to_bytes())));
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
};
nl_req
}
}