use super::messages::TcMessage;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum QdiscOptions {
FqCodel(FqCodelOptions),
Htb(HtbOptions),
Tbf(TbfOptions),
Netem(NetemOptions),
Prio(PrioOptions),
Sfq(SfqOptions),
FqPie(FqPieOptions),
Cake(CakeOptions),
Unknown(Vec<u8>),
}
#[derive(Debug, Clone, Default)]
pub struct FqCodelOptions {
pub target_us: u32,
pub interval_us: u32,
pub limit: u32,
pub flows: u32,
pub quantum: u32,
pub ecn: bool,
pub ce_threshold_us: Option<u32>,
pub memory_limit: Option<u32>,
pub drop_batch_size: Option<u32>,
}
impl FqCodelOptions {
#[inline]
pub fn target(&self) -> Option<std::time::Duration> {
if self.target_us > 0 {
Some(std::time::Duration::from_micros(self.target_us as u64))
} else {
None
}
}
#[inline]
pub fn interval(&self) -> Option<std::time::Duration> {
if self.interval_us > 0 {
Some(std::time::Duration::from_micros(self.interval_us as u64))
} else {
None
}
}
#[inline]
pub fn limit(&self) -> Option<u32> {
if self.limit > 0 {
Some(self.limit)
} else {
None
}
}
#[inline]
pub fn flows(&self) -> Option<u32> {
if self.flows > 0 {
Some(self.flows)
} else {
None
}
}
#[inline]
pub fn quantum(&self) -> Option<u32> {
if self.quantum > 0 {
Some(self.quantum)
} else {
None
}
}
#[inline]
pub fn ecn(&self) -> bool {
self.ecn
}
#[inline]
pub fn ce_threshold(&self) -> Option<std::time::Duration> {
self.ce_threshold_us
.map(|us| std::time::Duration::from_micros(us as u64))
}
#[inline]
pub fn memory_limit(&self) -> Option<u32> {
self.memory_limit
}
#[inline]
pub fn drop_batch_size(&self) -> Option<u32> {
self.drop_batch_size
}
}
#[derive(Debug, Clone, Default)]
pub struct HtbOptions {
pub default_class: u32,
pub rate2quantum: u32,
pub direct_qlen: Option<u32>,
pub version: u32,
}
impl HtbOptions {
#[inline]
pub fn default_class(&self) -> Option<u32> {
if self.default_class != 0 {
Some(self.default_class)
} else {
None
}
}
#[inline]
pub fn rate2quantum(&self) -> Option<u32> {
if self.rate2quantum != 0 {
Some(self.rate2quantum)
} else {
None
}
}
#[inline]
pub fn direct_qlen(&self) -> Option<u32> {
self.direct_qlen
}
#[inline]
pub fn version(&self) -> u32 {
self.version
}
}
#[derive(Debug, Clone, Default)]
pub struct TbfOptions {
pub rate: u64,
pub peakrate: u64,
pub burst: u32,
pub mtu: u32,
pub limit: u32,
}
impl TbfOptions {
#[inline]
pub fn rate(&self) -> Option<u64> {
if self.rate > 0 { Some(self.rate) } else { None }
}
#[inline]
pub fn peakrate(&self) -> Option<u64> {
if self.peakrate > 0 {
Some(self.peakrate)
} else {
None
}
}
#[inline]
pub fn burst(&self) -> Option<u32> {
if self.burst > 0 {
Some(self.burst)
} else {
None
}
}
#[inline]
pub fn mtu(&self) -> Option<u32> {
if self.mtu > 0 { Some(self.mtu) } else { None }
}
#[inline]
pub fn limit(&self) -> Option<u32> {
if self.limit > 0 {
Some(self.limit)
} else {
None
}
}
}
#[derive(Debug, Clone, Default)]
pub struct NetemOptions {
pub(crate) delay_ns: u64,
pub(crate) jitter_ns: u64,
pub(crate) delay_corr: f64,
pub(crate) loss_percent: f64,
pub(crate) loss_corr: f64,
pub(crate) duplicate_percent: f64,
pub(crate) duplicate_corr: f64,
pub(crate) reorder_percent: f64,
pub(crate) reorder_corr: f64,
pub(crate) corrupt_percent: f64,
pub(crate) corrupt_corr: f64,
pub(crate) rate: u64,
pub(crate) packet_overhead: i32,
pub(crate) cell_size: u32,
pub(crate) cell_overhead: i32,
pub(crate) limit: u32,
pub(crate) gap: u32,
pub(crate) ecn: bool,
pub(crate) slot: Option<NetemSlotOptions>,
pub(crate) loss_model: Option<NetemLossModel>,
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum NetemLossModel {
GilbertIntuitive {
p13: f64,
p31: f64,
p32: f64,
p14: f64,
p23: f64,
},
GilbertElliot {
p: f64,
r: f64,
h: f64,
k1: f64,
},
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NetemSlotOptions {
pub min_delay_ns: i64,
pub max_delay_ns: i64,
pub max_packets: i32,
pub max_bytes: i32,
pub dist_delay_ns: i64,
pub dist_jitter_ns: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum NetemParameter {
Delay,
Jitter,
Loss,
Duplicate,
Reorder,
Corrupt,
Rate,
}
impl NetemOptions {
#[inline]
pub fn delay(&self) -> Option<std::time::Duration> {
if self.delay_ns > 0 {
Some(std::time::Duration::from_nanos(self.delay_ns))
} else {
None
}
}
#[inline]
pub fn jitter(&self) -> Option<std::time::Duration> {
if self.jitter_ns > 0 {
Some(std::time::Duration::from_nanos(self.jitter_ns))
} else {
None
}
}
#[inline]
pub fn loss(&self) -> Option<f64> {
if self.loss_percent > 0.0 || self.loss_model.is_some() {
Some(self.loss_percent)
} else {
None
}
}
#[inline]
pub fn duplicate(&self) -> Option<f64> {
if self.duplicate_percent > 0.0 {
Some(self.duplicate_percent)
} else {
None
}
}
#[inline]
pub fn reorder(&self) -> Option<f64> {
if self.reorder_percent > 0.0 || self.gap > 0 {
Some(self.reorder_percent)
} else {
None
}
}
#[inline]
pub fn corrupt(&self) -> Option<f64> {
if self.corrupt_percent > 0.0 {
Some(self.corrupt_percent)
} else {
None
}
}
#[inline]
pub fn rate_bps(&self) -> Option<u64> {
if self.rate > 0 { Some(self.rate) } else { None }
}
#[inline]
pub fn packet_overhead(&self) -> i32 {
self.packet_overhead
}
#[inline]
pub fn cell_size(&self) -> u32 {
self.cell_size
}
#[inline]
pub fn cell_overhead(&self) -> i32 {
self.cell_overhead
}
#[inline]
pub fn delay_correlation(&self) -> Option<f64> {
if self.delay_corr > 0.0 {
Some(self.delay_corr)
} else {
None
}
}
#[inline]
pub fn loss_correlation(&self) -> Option<f64> {
if self.loss_corr > 0.0 {
Some(self.loss_corr)
} else {
None
}
}
#[inline]
pub fn duplicate_correlation(&self) -> Option<f64> {
if self.duplicate_corr > 0.0 {
Some(self.duplicate_corr)
} else {
None
}
}
#[inline]
pub fn reorder_correlation(&self) -> Option<f64> {
if self.reorder_corr > 0.0 {
Some(self.reorder_corr)
} else {
None
}
}
#[inline]
pub fn corrupt_correlation(&self) -> Option<f64> {
if self.corrupt_corr > 0.0 {
Some(self.corrupt_corr)
} else {
None
}
}
#[inline]
pub fn delay_ns(&self) -> u64 {
self.delay_ns
}
#[inline]
pub fn jitter_ns(&self) -> u64 {
self.jitter_ns
}
#[inline]
pub fn loss_percent(&self) -> f64 {
self.loss_percent
}
#[inline]
pub fn duplicate_percent(&self) -> f64 {
self.duplicate_percent
}
#[inline]
pub fn reorder_percent(&self) -> f64 {
self.reorder_percent
}
#[inline]
pub fn corrupt_percent(&self) -> f64 {
self.corrupt_percent
}
#[inline]
pub fn ecn(&self) -> bool {
self.ecn
}
#[inline]
pub fn gap(&self) -> Option<u32> {
if self.gap > 0 { Some(self.gap) } else { None }
}
#[inline]
pub fn limit(&self) -> Option<u32> {
if self.limit > 0 {
Some(self.limit)
} else {
None
}
}
#[inline]
pub fn slot(&self) -> Option<&NetemSlotOptions> {
self.slot.as_ref()
}
#[inline]
pub fn loss_model(&self) -> Option<&NetemLossModel> {
self.loss_model.as_ref()
}
pub fn configured_parameters(&self) -> Vec<NetemParameter> {
let mut params = Vec::new();
if self.delay().is_some() {
params.push(NetemParameter::Delay);
}
if self.jitter().is_some() {
params.push(NetemParameter::Jitter);
}
if self.loss().is_some() {
params.push(NetemParameter::Loss);
}
if self.duplicate().is_some() {
params.push(NetemParameter::Duplicate);
}
if self.reorder().is_some() {
params.push(NetemParameter::Reorder);
}
if self.corrupt().is_some() {
params.push(NetemParameter::Corrupt);
}
if self.rate_bps().is_some() {
params.push(NetemParameter::Rate);
}
params
}
pub fn requires_recreation_for(&self, new_config: &super::tc::NetemConfig) -> bool {
let removes_delay = self.delay().is_some() && new_config.delay.is_none();
let removes_jitter = self.jitter().is_some() && new_config.jitter.is_none();
let removes_loss = self.loss().is_some() && new_config.loss.is_zero();
let removes_duplicate = self.duplicate().is_some() && new_config.duplicate.is_zero();
let removes_reorder =
self.reorder().is_some() && new_config.reorder.is_zero() && new_config.gap == 0;
let removes_corrupt = self.corrupt().is_some() && new_config.corrupt.is_zero();
let removes_rate = self.rate_bps().is_some() && new_config.rate.is_none();
removes_delay
|| removes_jitter
|| removes_loss
|| removes_duplicate
|| removes_reorder
|| removes_corrupt
|| removes_rate
}
}
#[derive(Debug, Clone)]
pub struct PrioOptions {
pub bands: i32,
pub priomap: [u8; 16],
}
impl Default for PrioOptions {
fn default() -> Self {
Self {
bands: 3,
priomap: [1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SfqOptions {
pub quantum: u32,
pub perturb_period: i32,
pub limit: u32,
pub divisor: u32,
pub flows: u32,
pub depth: Option<u32>,
pub headdrop: Option<bool>,
}
#[derive(Debug, Clone, Default)]
pub struct FqPieOptions {
pub limit: u32,
pub flows: u32,
pub target_us: u32,
pub tupdate_us: u32,
pub alpha: u32,
pub beta: u32,
pub quantum: u32,
pub memory_limit: u32,
pub ecn_prob_permille: u32,
pub ecn: bool,
pub bytemode: bool,
pub dq_rate_estimator: bool,
}
impl FqPieOptions {
pub fn target(&self) -> Option<std::time::Duration> {
if self.target_us > 0 {
Some(std::time::Duration::from_micros(self.target_us as u64))
} else {
None
}
}
pub fn tupdate(&self) -> Option<std::time::Duration> {
if self.tupdate_us > 0 {
Some(std::time::Duration::from_micros(self.tupdate_us as u64))
} else {
None
}
}
pub fn ecn_prob(&self) -> Option<crate::util::Percent> {
if self.ecn_prob_permille > 0 {
Some(crate::util::Percent::new(
self.ecn_prob_permille as f64 / 10.0,
))
} else {
None
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CakeOptions {
pub bandwidth_bps: u64,
pub rtt_us: u32,
pub target_us: u32,
pub overhead: i32,
pub mpu: u32,
pub memory_limit: u32,
pub fwmark: u32,
pub diffserv_mode: u32,
pub flow_mode: u32,
pub atm_mode: u32,
pub ack_filter: u32,
pub autorate: bool,
pub nat: bool,
pub raw: bool,
pub wash: bool,
pub ingress: bool,
pub split_gso: bool,
}
impl CakeOptions {
pub fn bandwidth(&self) -> Option<crate::util::Rate> {
if self.bandwidth_bps > 0 {
Some(crate::util::Rate::bytes_per_sec(self.bandwidth_bps))
} else {
None
}
}
pub fn rtt(&self) -> Option<std::time::Duration> {
if self.rtt_us > 0 {
Some(std::time::Duration::from_micros(self.rtt_us as u64))
} else {
None
}
}
pub fn target(&self) -> Option<std::time::Duration> {
if self.target_us > 0 {
Some(std::time::Duration::from_micros(self.target_us as u64))
} else {
None
}
}
}
pub fn parse_qdisc_options(msg: &TcMessage) -> Option<QdiscOptions> {
let kind = msg.kind()?;
let data = msg.options.as_ref()?;
Some(match kind {
"fq_codel" => QdiscOptions::FqCodel(parse_fq_codel_options(data)),
"htb" => QdiscOptions::Htb(parse_htb_options(data)),
"tbf" => QdiscOptions::Tbf(parse_tbf_options(data)),
"netem" => QdiscOptions::Netem(parse_netem_options(data)),
"prio" => QdiscOptions::Prio(parse_prio_options(data)),
"sfq" => QdiscOptions::Sfq(parse_sfq_options(data)),
"fq_pie" => QdiscOptions::FqPie(parse_fq_pie_options(data)),
"cake" => QdiscOptions::Cake(parse_cake_options(data)),
_ => QdiscOptions::Unknown(data.clone()),
})
}
#[derive(Debug, Clone, Default)]
pub struct HtbClassOptions {
pub rate: u64,
pub ceil: u64,
pub burst: u32,
pub cburst: u32,
pub priority: u32,
pub quantum: u32,
pub level: u32,
}
pub fn parse_htb_class_options(data: &[u8]) -> Option<HtbClassOptions> {
use super::types::tc::qdisc::htb::*;
let mut opts = HtbClassOptions::default();
let mut rate64: Option<u64> = None;
let mut ceil64: Option<u64> = None;
let mut input = data;
while input.len() >= 4 {
let len = u16::from_ne_bytes(input[..2].try_into().ok()?) as usize;
let attr_type = u16::from_ne_bytes(input[2..4].try_into().ok()?);
if len < 4 || input.len() < len {
break;
}
let payload = &input[4..len];
match attr_type & 0x3FFF {
TCA_HTB_PARMS if payload.len() >= std::mem::size_of::<TcHtbOpt>() => {
let rate = u32::from_ne_bytes(payload[8..12].try_into().ok()?);
let ceil = u32::from_ne_bytes(payload[20..24].try_into().ok()?);
opts.rate = rate as u64;
opts.ceil = ceil as u64;
if payload.len() >= 44 {
opts.burst = u32::from_ne_bytes(payload[24..28].try_into().ok()?);
opts.cburst = u32::from_ne_bytes(payload[28..32].try_into().ok()?);
opts.quantum = u32::from_ne_bytes(payload[32..36].try_into().ok()?);
opts.level = u32::from_ne_bytes(payload[36..40].try_into().ok()?);
opts.priority = u32::from_ne_bytes(payload[40..44].try_into().ok()?);
}
}
TCA_HTB_RATE64 if payload.len() >= 8 => {
rate64 = Some(u64::from_ne_bytes(payload[..8].try_into().ok()?));
}
TCA_HTB_CEIL64 if payload.len() >= 8 => {
ceil64 = Some(u64::from_ne_bytes(payload[..8].try_into().ok()?));
}
_ => {}
}
let aligned = (len + 3) & !3;
if input.len() <= aligned {
break;
}
input = &input[aligned..];
}
if let Some(r) = rate64 {
opts.rate = r;
}
if let Some(c) = ceil64 {
opts.ceil = c;
}
Some(opts)
}
mod fq_codel_attrs {
pub const TCA_FQ_CODEL_TARGET: u16 = 1;
pub const TCA_FQ_CODEL_LIMIT: u16 = 2;
pub const TCA_FQ_CODEL_INTERVAL: u16 = 3;
pub const TCA_FQ_CODEL_ECN: u16 = 4;
pub const TCA_FQ_CODEL_FLOWS: u16 = 5;
pub const TCA_FQ_CODEL_QUANTUM: u16 = 6;
pub const TCA_FQ_CODEL_CE_THRESHOLD: u16 = 7;
pub const TCA_FQ_CODEL_DROP_BATCH_SIZE: u16 = 8;
pub const TCA_FQ_CODEL_MEMORY_LIMIT: u16 = 9;
}
fn parse_fq_codel_options(data: &[u8]) -> FqCodelOptions {
use fq_codel_attrs::*;
let mut opts = FqCodelOptions::default();
let mut input = data;
while input.len() >= 4 {
let Some(len) = input
.get(..2)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
let len = len as usize;
let Some(attr_type) = input
.get(2..4)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
if len < 4 || input.len() < len {
break;
}
let payload = &input[4..len];
match attr_type & 0x3FFF {
TCA_FQ_CODEL_TARGET if payload.len() >= 4 => {
opts.target_us = u32::from_ne_bytes(payload[..4].try_into().unwrap());
}
TCA_FQ_CODEL_LIMIT if payload.len() >= 4 => {
opts.limit = u32::from_ne_bytes(payload[..4].try_into().unwrap());
}
TCA_FQ_CODEL_INTERVAL if payload.len() >= 4 => {
opts.interval_us = u32::from_ne_bytes(payload[..4].try_into().unwrap());
}
TCA_FQ_CODEL_ECN if payload.len() >= 4 => {
opts.ecn = u32::from_ne_bytes(payload[..4].try_into().unwrap()) != 0;
}
TCA_FQ_CODEL_FLOWS if payload.len() >= 4 => {
opts.flows = u32::from_ne_bytes(payload[..4].try_into().unwrap());
}
TCA_FQ_CODEL_QUANTUM if payload.len() >= 4 => {
opts.quantum = u32::from_ne_bytes(payload[..4].try_into().unwrap());
}
TCA_FQ_CODEL_CE_THRESHOLD if payload.len() >= 4 => {
opts.ce_threshold_us = Some(u32::from_ne_bytes(payload[..4].try_into().unwrap()));
}
TCA_FQ_CODEL_DROP_BATCH_SIZE if payload.len() >= 4 => {
opts.drop_batch_size = Some(u32::from_ne_bytes(payload[..4].try_into().unwrap()));
}
TCA_FQ_CODEL_MEMORY_LIMIT if payload.len() >= 4 => {
opts.memory_limit = Some(u32::from_ne_bytes(payload[..4].try_into().unwrap()));
}
_ => {}
}
let aligned = (len + 3) & !3;
if input.len() <= aligned {
break;
}
input = &input[aligned..];
}
opts
}
fn parse_htb_options(data: &[u8]) -> HtbOptions {
use super::types::tc::qdisc::htb::*;
let mut opts = HtbOptions::default();
let mut input = data;
while input.len() >= 4 {
let Some(len) = input
.get(..2)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
let len = len as usize;
let Some(attr_type) = input
.get(2..4)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
if len < 4 || input.len() < len {
break;
}
let payload = &input[4..len];
match attr_type & 0x3FFF {
TCA_HTB_INIT if payload.len() >= TcHtbGlob::SIZE => {
opts.version = u32::from_ne_bytes(payload[0..4].try_into().unwrap());
opts.rate2quantum = u32::from_ne_bytes(payload[4..8].try_into().unwrap());
opts.default_class = u32::from_ne_bytes(payload[8..12].try_into().unwrap());
}
TCA_HTB_DIRECT_QLEN if payload.len() >= 4 => {
opts.direct_qlen = Some(u32::from_ne_bytes(payload[..4].try_into().unwrap()));
}
_ => {}
}
let aligned = (len + 3) & !3;
if input.len() <= aligned {
break;
}
input = &input[aligned..];
}
opts
}
fn parse_tbf_options(data: &[u8]) -> TbfOptions {
use super::types::tc::qdisc::tbf::*;
let mut opts = TbfOptions::default();
let mut rate64: Option<u64> = None;
let mut prate64: Option<u64> = None;
let mut input = data;
while input.len() >= 4 {
let Some(len) = input
.get(..2)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
let len = len as usize;
let Some(attr_type) = input
.get(2..4)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
if len < 4 || input.len() < len {
break;
}
let payload = &input[4..len];
match attr_type & 0x3FFF {
TCA_TBF_PARMS
if payload.len() >= 36 => {
opts.rate = u32::from_ne_bytes(payload[8..12].try_into().unwrap()) as u64;
opts.peakrate = u32::from_ne_bytes(payload[20..24].try_into().unwrap()) as u64;
opts.limit = u32::from_ne_bytes(payload[24..28].try_into().unwrap());
opts.burst = u32::from_ne_bytes(payload[28..32].try_into().unwrap());
opts.mtu = u32::from_ne_bytes(payload[32..36].try_into().unwrap());
}
TCA_TBF_RATE64
if payload.len() >= 8 => {
rate64 = Some(u64::from_ne_bytes(payload[..8].try_into().unwrap()));
}
TCA_TBF_PRATE64
if payload.len() >= 8 => {
prate64 = Some(u64::from_ne_bytes(payload[..8].try_into().unwrap()));
}
TCA_TBF_BURST
if payload.len() >= 4 => {
opts.burst = u32::from_ne_bytes(payload[..4].try_into().unwrap());
}
_ => {}
}
let aligned = (len + 3) & !3;
if input.len() <= aligned {
break;
}
input = &input[aligned..];
}
if let Some(r) = rate64 {
opts.rate = r;
}
if let Some(r) = prate64 {
opts.peakrate = r;
}
opts
}
fn parse_netem_options(data: &[u8]) -> NetemOptions {
use super::types::tc::qdisc::netem::*;
let mut opts = NetemOptions::default();
if data.len() >= TcNetemQopt::SIZE {
let delay_us = u32::from_ne_bytes(data[0..4].try_into().unwrap());
opts.delay_ns = delay_us as u64 * 1000;
opts.limit = u32::from_ne_bytes(data[4..8].try_into().unwrap());
let loss_raw = u32::from_ne_bytes(data[8..12].try_into().unwrap());
opts.loss_percent = prob_to_percent(loss_raw);
opts.gap = u32::from_ne_bytes(data[12..16].try_into().unwrap());
let dup_raw = u32::from_ne_bytes(data[16..20].try_into().unwrap());
opts.duplicate_percent = prob_to_percent(dup_raw);
let jitter_us = u32::from_ne_bytes(data[20..24].try_into().unwrap());
opts.jitter_ns = jitter_us as u64 * 1000;
}
let mut input = if data.len() > TcNetemQopt::SIZE {
&data[TcNetemQopt::SIZE..]
} else {
return opts;
};
while input.len() >= 4 {
let Some(len) = input
.get(..2)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
let len = len as usize;
let Some(attr_type) = input
.get(2..4)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
if len < 4 || input.len() < len {
break;
}
let payload = &input[4..len];
match attr_type & 0x3FFF {
TCA_NETEM_CORR
if payload.len() >= TcNetemCorr::SIZE => {
let delay_corr = u32::from_ne_bytes(payload[0..4].try_into().unwrap());
let loss_corr = u32::from_ne_bytes(payload[4..8].try_into().unwrap());
let dup_corr = u32::from_ne_bytes(payload[8..12].try_into().unwrap());
opts.delay_corr = prob_to_percent(delay_corr);
opts.loss_corr = prob_to_percent(loss_corr);
opts.duplicate_corr = prob_to_percent(dup_corr);
}
TCA_NETEM_REORDER
if payload.len() >= TcNetemReorder::SIZE => {
let prob = u32::from_ne_bytes(payload[0..4].try_into().unwrap());
let corr = u32::from_ne_bytes(payload[4..8].try_into().unwrap());
opts.reorder_percent = prob_to_percent(prob);
opts.reorder_corr = prob_to_percent(corr);
}
TCA_NETEM_CORRUPT
if payload.len() >= TcNetemCorrupt::SIZE => {
let prob = u32::from_ne_bytes(payload[0..4].try_into().unwrap());
let corr = u32::from_ne_bytes(payload[4..8].try_into().unwrap());
opts.corrupt_percent = prob_to_percent(prob);
opts.corrupt_corr = prob_to_percent(corr);
}
TCA_NETEM_RATE => {
if payload.len() >= 4 {
opts.rate = u32::from_ne_bytes(payload[0..4].try_into().unwrap()) as u64;
}
if payload.len() >= TcNetemRate::SIZE {
opts.packet_overhead = i32::from_ne_bytes(payload[4..8].try_into().unwrap());
opts.cell_size = u32::from_ne_bytes(payload[8..12].try_into().unwrap());
opts.cell_overhead = i32::from_ne_bytes(payload[12..16].try_into().unwrap());
}
}
TCA_NETEM_RATE64
if payload.len() >= 8 => {
opts.rate = u64::from_ne_bytes(payload[..8].try_into().unwrap());
}
TCA_NETEM_ECN => {
opts.ecn = true;
}
TCA_NETEM_LATENCY64
if payload.len() >= 8 => {
opts.delay_ns = u64::from_ne_bytes(payload[..8].try_into().unwrap());
}
TCA_NETEM_JITTER64
if payload.len() >= 8 => {
opts.jitter_ns = u64::from_ne_bytes(payload[..8].try_into().unwrap());
}
TCA_NETEM_SLOT
if payload.len() >= TcNetemSlot::SIZE => {
opts.slot = Some(NetemSlotOptions {
min_delay_ns: i64::from_ne_bytes(payload[0..8].try_into().unwrap()),
max_delay_ns: i64::from_ne_bytes(payload[8..16].try_into().unwrap()),
max_packets: i32::from_ne_bytes(payload[16..20].try_into().unwrap()),
max_bytes: i32::from_ne_bytes(payload[20..24].try_into().unwrap()),
dist_delay_ns: i64::from_ne_bytes(payload[24..32].try_into().unwrap()),
dist_jitter_ns: i64::from_ne_bytes(payload[32..40].try_into().unwrap()),
});
}
TCA_NETEM_LOSS => {
parse_netem_loss_model(payload, &mut opts);
}
_ => {}
}
let aligned = (len + 3) & !3;
if input.len() <= aligned {
break;
}
input = &input[aligned..];
}
opts
}
fn parse_netem_loss_model(data: &[u8], opts: &mut NetemOptions) {
use super::types::tc::qdisc::netem::*;
let mut input = data;
while input.len() >= 4 {
let Some(len) = input
.get(..2)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
let len = len as usize;
let Some(attr_type) = input
.get(2..4)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
if len < 4 || input.len() < len {
break;
}
let payload = &input[4..len];
match attr_type & 0x3FFF {
NETEM_LOSS_GI
if payload.len() >= TcNetemGiModel::SIZE => {
let p13 = u32::from_ne_bytes(payload[0..4].try_into().unwrap());
let p31 = u32::from_ne_bytes(payload[4..8].try_into().unwrap());
let p32 = u32::from_ne_bytes(payload[8..12].try_into().unwrap());
let p14 = u32::from_ne_bytes(payload[12..16].try_into().unwrap());
let p23 = u32::from_ne_bytes(payload[16..20].try_into().unwrap());
opts.loss_model = Some(NetemLossModel::GilbertIntuitive {
p13: prob_to_percent(p13),
p31: prob_to_percent(p31),
p32: prob_to_percent(p32),
p14: prob_to_percent(p14),
p23: prob_to_percent(p23),
});
}
NETEM_LOSS_GE
if payload.len() >= TcNetemGeModel::SIZE => {
let p = u32::from_ne_bytes(payload[0..4].try_into().unwrap());
let r = u32::from_ne_bytes(payload[4..8].try_into().unwrap());
let h = u32::from_ne_bytes(payload[8..12].try_into().unwrap());
let k1 = u32::from_ne_bytes(payload[12..16].try_into().unwrap());
opts.loss_model = Some(NetemLossModel::GilbertElliot {
p: prob_to_percent(p),
r: prob_to_percent(r),
h: prob_to_percent(h),
k1: prob_to_percent(k1),
});
}
_ => {}
}
let aligned = (len + 3) & !3;
if input.len() <= aligned {
break;
}
input = &input[aligned..];
}
}
fn parse_prio_options(data: &[u8]) -> PrioOptions {
if data.len() >= 20 {
let bands = i32::from_ne_bytes(data[0..4].try_into().unwrap());
let mut priomap = [0u8; 16];
priomap.copy_from_slice(&data[4..20]);
PrioOptions { bands, priomap }
} else {
PrioOptions::default()
}
}
fn parse_fq_pie_options(data: &[u8]) -> FqPieOptions {
use super::types::tc::qdisc::fq_pie::*;
let mut opts = FqPieOptions::default();
let mut input = data;
while input.len() >= 4 {
let Some(len) = input
.get(..2)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
let len = len as usize;
let Some(attr_type) = input
.get(2..4)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
if len < 4 || input.len() < len {
break;
}
let payload = &input[4..len];
let read_u32 = |p: &[u8]| -> Option<u32> {
p.get(..4)
.and_then(|b| b.try_into().ok())
.map(u32::from_ne_bytes)
};
match attr_type & 0x3FFF {
TCA_FQ_PIE_LIMIT => {
if let Some(v) = read_u32(payload) {
opts.limit = v;
}
}
TCA_FQ_PIE_FLOWS => {
if let Some(v) = read_u32(payload) {
opts.flows = v;
}
}
TCA_FQ_PIE_TARGET => {
if let Some(v) = read_u32(payload) {
opts.target_us = v;
}
}
TCA_FQ_PIE_TUPDATE => {
if let Some(v) = read_u32(payload) {
opts.tupdate_us = v;
}
}
TCA_FQ_PIE_ALPHA => {
if let Some(v) = read_u32(payload) {
opts.alpha = v;
}
}
TCA_FQ_PIE_BETA => {
if let Some(v) = read_u32(payload) {
opts.beta = v;
}
}
TCA_FQ_PIE_QUANTUM => {
if let Some(v) = read_u32(payload) {
opts.quantum = v;
}
}
TCA_FQ_PIE_MEMORY_LIMIT => {
if let Some(v) = read_u32(payload) {
opts.memory_limit = v;
}
}
TCA_FQ_PIE_ECN_PROB => {
if let Some(v) = read_u32(payload) {
opts.ecn_prob_permille = v;
}
}
TCA_FQ_PIE_ECN => {
if let Some(v) = read_u32(payload) {
opts.ecn = v != 0;
}
}
TCA_FQ_PIE_BYTEMODE => {
if let Some(v) = read_u32(payload) {
opts.bytemode = v != 0;
}
}
TCA_FQ_PIE_DQ_RATE_ESTIMATOR => {
if let Some(v) = read_u32(payload) {
opts.dq_rate_estimator = v != 0;
}
}
_ => {}
}
let aligned = (len + 3) & !3;
if input.len() <= aligned {
break;
}
input = &input[aligned..];
}
opts
}
fn parse_cake_options(data: &[u8]) -> CakeOptions {
use super::types::tc::qdisc::cake::*;
let mut opts = CakeOptions::default();
let mut input = data;
while input.len() >= 4 {
let Some(len) = input
.get(..2)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
let len = len as usize;
let Some(attr_type) = input
.get(2..4)
.and_then(|b| b.try_into().ok())
.map(u16::from_ne_bytes)
else {
break;
};
if len < 4 || input.len() < len {
break;
}
let payload = &input[4..len];
let read_u32 = |p: &[u8]| -> Option<u32> {
p.get(..4)
.and_then(|b| b.try_into().ok())
.map(u32::from_ne_bytes)
};
let read_i32 = |p: &[u8]| -> Option<i32> {
p.get(..4)
.and_then(|b| b.try_into().ok())
.map(i32::from_ne_bytes)
};
let read_u64 = |p: &[u8]| -> Option<u64> {
p.get(..8)
.and_then(|b| b.try_into().ok())
.map(u64::from_ne_bytes)
};
match attr_type & 0x3FFF {
TCA_CAKE_BASE_RATE64 => {
if let Some(v) = read_u64(payload) {
opts.bandwidth_bps = v;
}
}
TCA_CAKE_RTT => {
if let Some(v) = read_u32(payload) {
opts.rtt_us = v;
}
}
TCA_CAKE_TARGET => {
if let Some(v) = read_u32(payload) {
opts.target_us = v;
}
}
TCA_CAKE_OVERHEAD => {
if let Some(v) = read_i32(payload) {
opts.overhead = v;
}
}
TCA_CAKE_MPU => {
if let Some(v) = read_u32(payload) {
opts.mpu = v;
}
}
TCA_CAKE_MEMORY => {
if let Some(v) = read_u32(payload) {
opts.memory_limit = v;
}
}
TCA_CAKE_FWMARK => {
if let Some(v) = read_u32(payload) {
opts.fwmark = v;
}
}
TCA_CAKE_DIFFSERV_MODE => {
if let Some(v) = read_u32(payload) {
opts.diffserv_mode = v;
}
}
TCA_CAKE_FLOW_MODE => {
if let Some(v) = read_u32(payload) {
opts.flow_mode = v;
}
}
TCA_CAKE_ATM => {
if let Some(v) = read_u32(payload) {
opts.atm_mode = v;
}
}
TCA_CAKE_ACK_FILTER => {
if let Some(v) = read_u32(payload) {
opts.ack_filter = v;
}
}
TCA_CAKE_AUTORATE => {
if let Some(v) = read_u32(payload) {
opts.autorate = v != 0;
}
}
TCA_CAKE_NAT => {
if let Some(v) = read_u32(payload) {
opts.nat = v != 0;
}
}
TCA_CAKE_RAW => {
if let Some(v) = read_u32(payload) {
opts.raw = v != 0;
}
}
TCA_CAKE_WASH => {
if let Some(v) = read_u32(payload) {
opts.wash = v != 0;
}
}
TCA_CAKE_INGRESS => {
if let Some(v) = read_u32(payload) {
opts.ingress = v != 0;
}
}
TCA_CAKE_SPLIT_GSO => {
if let Some(v) = read_u32(payload) {
opts.split_gso = v != 0;
}
}
_ => {}
}
let aligned = (len + 3) & !3;
if input.len() <= aligned {
break;
}
input = &input[aligned..];
}
opts
}
fn parse_sfq_options(data: &[u8]) -> SfqOptions {
if data.len() >= 20 {
let quantum = u32::from_ne_bytes(data[0..4].try_into().unwrap());
let perturb_period = i32::from_ne_bytes(data[4..8].try_into().unwrap());
let limit = u32::from_ne_bytes(data[8..12].try_into().unwrap());
let divisor = u32::from_ne_bytes(data[12..16].try_into().unwrap());
let flows = u32::from_ne_bytes(data[16..20].try_into().unwrap());
let mut opts = SfqOptions {
quantum,
perturb_period,
limit,
divisor,
flows,
..Default::default()
};
if data.len() >= 32 {
opts.depth = Some(u32::from_ne_bytes(data[20..24].try_into().unwrap()));
let headdrop = u32::from_ne_bytes(data[24..28].try_into().unwrap());
opts.headdrop = Some(headdrop != 0);
}
opts
} else {
SfqOptions::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fq_codel_defaults() {
let opts = FqCodelOptions::default();
assert_eq!(opts.target_us, 0);
assert_eq!(opts.interval_us, 0);
assert!(!opts.ecn);
}
#[test]
fn test_prio_defaults() {
let opts = PrioOptions::default();
assert_eq!(opts.bands, 3);
assert_eq!(opts.priomap[0], 1);
assert_eq!(opts.priomap[6], 0);
}
#[test]
fn test_netem_defaults() {
let opts = NetemOptions::default();
assert_eq!(opts.delay_ns, 0);
assert!(opts.delay().is_none());
assert_eq!(opts.jitter_ns, 0);
assert!(opts.jitter().is_none());
assert!(opts.loss().is_none());
assert!(opts.duplicate().is_none());
assert!(opts.reorder().is_none());
assert!(opts.corrupt().is_none());
assert!(opts.rate_bps().is_none());
assert_eq!(opts.limit, 0);
assert_eq!(opts.gap, 0);
assert!(!opts.ecn);
assert!(opts.slot.is_none());
}
#[test]
fn test_netem_delay() {
let mut opts = NetemOptions::default();
assert!(opts.delay().is_none());
opts.delay_ns = 100_000_000; assert_eq!(opts.delay(), Some(std::time::Duration::from_millis(100)));
}
#[test]
fn test_netem_jitter() {
let mut opts = NetemOptions::default();
assert!(opts.jitter().is_none());
opts.jitter_ns = 10_000_000; assert_eq!(opts.jitter(), Some(std::time::Duration::from_millis(10)));
}
#[test]
fn test_netem_loss() {
let mut opts = NetemOptions::default();
assert!(opts.loss().is_none());
opts.loss_percent = 1.0;
assert_eq!(opts.loss(), Some(1.0));
opts.loss_percent = 0.0;
opts.loss_model = Some(NetemLossModel::GilbertElliot {
p: 1.0,
r: 10.0,
h: 50.0,
k1: 0.0,
});
assert!(opts.loss().is_some());
}
#[test]
fn test_netem_duplicate() {
let mut opts = NetemOptions::default();
assert!(opts.duplicate().is_none());
opts.duplicate_percent = 0.5;
assert_eq!(opts.duplicate(), Some(0.5));
}
#[test]
fn test_netem_reorder() {
let mut opts = NetemOptions::default();
assert!(opts.reorder().is_none());
opts.reorder_percent = 5.0;
assert_eq!(opts.reorder(), Some(5.0));
opts.reorder_percent = 0.0;
opts.gap = 5;
assert_eq!(opts.reorder(), Some(0.0));
}
#[test]
fn test_netem_corrupt() {
let mut opts = NetemOptions::default();
assert!(opts.corrupt().is_none());
opts.corrupt_percent = 0.1;
assert_eq!(opts.corrupt(), Some(0.1));
}
#[test]
fn test_netem_rate_bps() {
let mut opts = NetemOptions::default();
assert!(opts.rate_bps().is_none());
opts.rate = 1_000_000;
assert_eq!(opts.rate_bps(), Some(1_000_000));
}
#[test]
fn test_netem_configured_parameters() {
let opts = NetemOptions::default();
assert!(opts.configured_parameters().is_empty());
let opts = NetemOptions {
delay_ns: 100_000_000,
jitter_ns: 10_000_000,
loss_percent: 1.0,
rate: 1_000_000,
..Default::default()
};
let params = opts.configured_parameters();
assert_eq!(params.len(), 4);
assert!(params.contains(&NetemParameter::Delay));
assert!(params.contains(&NetemParameter::Jitter));
assert!(params.contains(&NetemParameter::Loss));
assert!(params.contains(&NetemParameter::Rate));
assert!(!params.contains(&NetemParameter::Duplicate));
assert!(!params.contains(&NetemParameter::Reorder));
assert!(!params.contains(&NetemParameter::Corrupt));
}
#[test]
fn test_netem_parse_basic() {
use std::time::Duration;
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.latency = 100_000; qopt.limit = 1000;
qopt.loss = percent_to_prob(1.0); qopt.gap = 0;
qopt.duplicate = 0;
qopt.jitter = 10_000;
let data = qopt.as_bytes().to_vec();
let opts = parse_netem_options(&data);
assert_eq!(opts.delay(), Some(Duration::from_millis(100)));
assert_eq!(opts.jitter(), Some(Duration::from_millis(10)));
assert_eq!(opts.limit, 1000);
assert!((opts.loss_percent - 1.0).abs() < 0.01);
assert_eq!(opts.duplicate_percent, 0.0);
assert_eq!(opts.gap, 0);
}
#[test]
fn test_netem_parse_with_correlation() {
use std::time::Duration;
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.latency = 50_000; qopt.limit = 1000;
qopt.loss = percent_to_prob(5.0); qopt.duplicate = percent_to_prob(2.0); qopt.jitter = 5_000;
let corr = TcNetemCorr {
delay_corr: percent_to_prob(25.0),
loss_corr: percent_to_prob(50.0),
dup_corr: percent_to_prob(10.0),
};
let mut data = qopt.as_bytes().to_vec();
let corr_bytes = corr.as_bytes();
let attr_len = 4 + corr_bytes.len(); data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_CORR.to_ne_bytes());
data.extend_from_slice(corr_bytes);
let opts = parse_netem_options(&data);
assert_eq!(opts.delay(), Some(Duration::from_micros(50_000)));
assert_eq!(opts.jitter(), Some(Duration::from_micros(5_000)));
assert!((opts.loss_percent - 5.0).abs() < 0.1);
assert!((opts.duplicate_percent - 2.0).abs() < 0.1);
assert!((opts.delay_corr - 25.0).abs() < 0.1);
assert!((opts.loss_corr - 50.0).abs() < 0.1);
assert!((opts.duplicate_corr - 10.0).abs() < 0.1);
}
#[test]
fn test_netem_parse_with_reorder() {
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.latency = 100_000;
qopt.limit = 1000;
qopt.gap = 5;
let reorder = TcNetemReorder {
probability: percent_to_prob(10.0),
correlation: percent_to_prob(25.0),
};
let mut data = qopt.as_bytes().to_vec();
let reorder_bytes = reorder.as_bytes();
let attr_len = 4 + reorder_bytes.len();
data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_REORDER.to_ne_bytes());
data.extend_from_slice(reorder_bytes);
let opts = parse_netem_options(&data);
assert_eq!(opts.gap, 5);
assert!((opts.reorder_percent - 10.0).abs() < 0.1);
assert!((opts.reorder_corr - 25.0).abs() < 0.1);
}
#[test]
fn test_netem_parse_with_corrupt() {
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.latency = 0;
qopt.limit = 1000;
let corrupt = TcNetemCorrupt {
probability: percent_to_prob(0.5),
correlation: percent_to_prob(10.0),
};
let mut data = qopt.as_bytes().to_vec();
let corrupt_bytes = corrupt.as_bytes();
let attr_len = 4 + corrupt_bytes.len();
data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_CORRUPT.to_ne_bytes());
data.extend_from_slice(corrupt_bytes);
let opts = parse_netem_options(&data);
assert!((opts.corrupt_percent - 0.5).abs() < 0.1);
assert!((opts.corrupt_corr - 10.0).abs() < 0.1);
}
#[test]
fn test_netem_parse_with_rate() {
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.limit = 1000;
let rate = TcNetemRate {
rate: 1_000_000, ..Default::default()
};
let mut data = qopt.as_bytes().to_vec();
let rate_bytes = rate.as_bytes();
let attr_len = 4 + rate_bytes.len();
data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_RATE.to_ne_bytes());
data.extend_from_slice(rate_bytes);
let opts = parse_netem_options(&data);
assert_eq!(opts.rate, 1_000_000);
}
#[test]
fn test_netem_parse_with_rate64() {
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.limit = 1000;
let rate64: u64 = 10_000_000_000;
let mut data = qopt.as_bytes().to_vec();
let attr_len = 4 + 8; data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_RATE64.to_ne_bytes());
data.extend_from_slice(&rate64.to_ne_bytes());
let opts = parse_netem_options(&data);
assert_eq!(opts.rate, 10_000_000_000);
}
#[test]
fn test_netem_parse_multiple_attrs() {
use std::time::Duration;
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.latency = 100_000; qopt.limit = 1000;
qopt.loss = percent_to_prob(1.0);
qopt.jitter = 10_000;
let corr = TcNetemCorr {
delay_corr: percent_to_prob(25.0),
loss_corr: percent_to_prob(50.0),
..Default::default()
};
let corrupt = TcNetemCorrupt {
probability: percent_to_prob(0.1),
..Default::default()
};
let mut data = qopt.as_bytes().to_vec();
let corr_bytes = corr.as_bytes();
let attr_len = 4 + corr_bytes.len();
let aligned_len = (attr_len + 3) & !3;
data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_CORR.to_ne_bytes());
data.extend_from_slice(corr_bytes);
data.resize(data.len() + aligned_len - attr_len, 0);
let corrupt_bytes = corrupt.as_bytes();
let attr_len = 4 + corrupt_bytes.len();
data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_CORRUPT.to_ne_bytes());
data.extend_from_slice(corrupt_bytes);
let opts = parse_netem_options(&data);
assert_eq!(opts.delay(), Some(Duration::from_millis(100)));
assert_eq!(opts.jitter(), Some(Duration::from_millis(10)));
assert!((opts.loss_percent - 1.0).abs() < 0.1);
assert!((opts.delay_corr - 25.0).abs() < 0.1);
assert!((opts.loss_corr - 50.0).abs() < 0.1);
assert!((opts.corrupt_percent - 0.1).abs() < 0.1);
}
#[test]
fn test_netem_parse_with_64bit_delay() {
use std::time::Duration;
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.latency = 100_000; qopt.limit = 1000;
let delay64_ns: u64 = 5_000_000_000;
let jitter64_ns: u64 = 500_000_000;
let mut data = qopt.as_bytes().to_vec();
let attr_len = 4 + 8;
let aligned_len = (attr_len + 3) & !3;
data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_LATENCY64.to_ne_bytes());
data.extend_from_slice(&delay64_ns.to_ne_bytes());
data.resize(data.len() + aligned_len - attr_len, 0);
data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_JITTER64.to_ne_bytes());
data.extend_from_slice(&jitter64_ns.to_ne_bytes());
let opts = parse_netem_options(&data);
assert_eq!(opts.delay_ns, 5_000_000_000);
assert_eq!(opts.jitter_ns, 500_000_000);
assert_eq!(opts.delay(), Some(Duration::from_secs(5)));
assert_eq!(opts.jitter(), Some(Duration::from_millis(500)));
}
#[test]
fn test_netem_parse_with_ecn() {
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.limit = 1000;
let mut data = qopt.as_bytes().to_vec();
let attr_len = 4 + 4; data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_ECN.to_ne_bytes());
data.extend_from_slice(&1u32.to_ne_bytes());
let opts = parse_netem_options(&data);
assert!(opts.ecn);
}
#[test]
fn test_netem_parse_with_slot() {
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.limit = 1000;
let slot = TcNetemSlot {
min_delay: 1_000_000, max_delay: 10_000_000, max_packets: 10,
max_bytes: 15000,
dist_delay: 0,
dist_jitter: 0,
};
let mut data = qopt.as_bytes().to_vec();
let slot_bytes = slot.as_bytes();
let attr_len = 4 + slot_bytes.len();
data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_SLOT.to_ne_bytes());
data.extend_from_slice(slot_bytes);
let opts = parse_netem_options(&data);
assert!(opts.slot.is_some());
let slot_opts = opts.slot.unwrap();
assert_eq!(slot_opts.min_delay_ns, 1_000_000);
assert_eq!(slot_opts.max_delay_ns, 10_000_000);
assert_eq!(slot_opts.max_packets, 10);
assert_eq!(slot_opts.max_bytes, 15000);
}
#[test]
fn test_netem_parse_with_rate_overhead() {
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.limit = 1000;
let rate = TcNetemRate {
rate: 1_000_000, packet_overhead: 14, cell_size: 53, cell_overhead: 5, };
let mut data = qopt.as_bytes().to_vec();
let rate_bytes = rate.as_bytes();
let attr_len = 4 + rate_bytes.len();
data.extend_from_slice(&(attr_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_RATE.to_ne_bytes());
data.extend_from_slice(rate_bytes);
let opts = parse_netem_options(&data);
assert_eq!(opts.rate, 1_000_000);
assert_eq!(opts.packet_overhead, 14);
assert_eq!(opts.cell_size, 53);
assert_eq!(opts.cell_overhead, 5);
}
#[test]
fn test_netem_prob_conversion_roundtrip() {
use super::super::types::tc::qdisc::netem::*;
let test_values = [0.0, 0.1, 1.0, 10.0, 50.0, 99.9, 100.0];
for &percent in &test_values {
let prob = percent_to_prob(percent);
let back = prob_to_percent(prob);
assert!(
(percent - back).abs() < 0.01,
"Roundtrip failed for {}: got {}",
percent,
back
);
}
}
#[test]
fn test_netem_parse_with_loss_model_gi() {
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.limit = 1000;
let gi_model = TcNetemGiModel {
p13: percent_to_prob(5.0),
p31: percent_to_prob(95.0),
p32: percent_to_prob(3.0),
p14: percent_to_prob(1.0),
p23: percent_to_prob(10.0),
};
let mut data = qopt.as_bytes().to_vec();
let gi_bytes = gi_model.as_bytes();
let inner_len = 4 + gi_bytes.len();
let inner_aligned = (inner_len + 3) & !3;
let mut nested = Vec::new();
nested.extend_from_slice(&(inner_len as u16).to_ne_bytes());
nested.extend_from_slice(&NETEM_LOSS_GI.to_ne_bytes());
nested.extend_from_slice(gi_bytes);
nested.resize(nested.len() + inner_aligned - inner_len, 0);
let outer_len = 4 + nested.len();
data.extend_from_slice(&(outer_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_LOSS.to_ne_bytes());
data.extend_from_slice(&nested);
let opts = parse_netem_options(&data);
assert!(opts.loss_model.is_some());
match opts.loss_model.unwrap() {
NetemLossModel::GilbertIntuitive {
p13,
p31,
p32,
p14,
p23,
} => {
assert!((p13 - 5.0).abs() < 0.1);
assert!((p31 - 95.0).abs() < 0.1);
assert!((p32 - 3.0).abs() < 0.1);
assert!((p14 - 1.0).abs() < 0.1);
assert!((p23 - 10.0).abs() < 0.1);
}
_ => panic!("Expected GilbertIntuitive model"),
}
}
#[test]
fn test_netem_parse_with_loss_model_ge() {
use super::super::types::tc::qdisc::netem::*;
let mut qopt = TcNetemQopt::new();
qopt.limit = 1000;
let ge_model = TcNetemGeModel {
p: percent_to_prob(1.0),
r: percent_to_prob(10.0),
h: percent_to_prob(50.0),
k1: percent_to_prob(0.0),
};
let mut data = qopt.as_bytes().to_vec();
let ge_bytes = ge_model.as_bytes();
let inner_len = 4 + ge_bytes.len();
let inner_aligned = (inner_len + 3) & !3;
let mut nested = Vec::new();
nested.extend_from_slice(&(inner_len as u16).to_ne_bytes());
nested.extend_from_slice(&NETEM_LOSS_GE.to_ne_bytes());
nested.extend_from_slice(ge_bytes);
nested.resize(nested.len() + inner_aligned - inner_len, 0);
let outer_len = 4 + nested.len();
data.extend_from_slice(&(outer_len as u16).to_ne_bytes());
data.extend_from_slice(&TCA_NETEM_LOSS.to_ne_bytes());
data.extend_from_slice(&nested);
let opts = parse_netem_options(&data);
assert!(opts.loss_model.is_some());
match opts.loss_model.unwrap() {
NetemLossModel::GilbertElliot { p, r, h, k1 } => {
assert!((p - 1.0).abs() < 0.1);
assert!((r - 10.0).abs() < 0.1);
assert!((h - 50.0).abs() < 0.1);
assert!((k1 - 0.0).abs() < 0.1);
}
_ => panic!("Expected GilbertElliot model"),
}
}
#[test]
fn test_requires_recreation_removing_delay() {
use std::time::Duration;
use super::super::tc::NetemConfig;
let current = NetemOptions {
delay_ns: 100_000_000, ..Default::default()
};
let new_config = NetemConfig::new().build();
assert!(current.requires_recreation_for(&new_config));
let new_config = NetemConfig::new().delay(Duration::from_millis(50)).build();
assert!(!current.requires_recreation_for(&new_config));
}
#[test]
fn test_requires_recreation_removing_loss() {
use std::time::Duration;
use super::super::tc::NetemConfig;
let current = NetemOptions {
delay_ns: 100_000_000,
loss_percent: 1.0,
..Default::default()
};
let new_config = NetemConfig::new().delay(Duration::from_millis(100)).build();
assert!(current.requires_recreation_for(&new_config));
let new_config = NetemConfig::new()
.delay(Duration::from_millis(50))
.loss(crate::util::Percent::new(0.5))
.build();
assert!(!current.requires_recreation_for(&new_config));
}
#[test]
fn test_requires_recreation_removing_jitter() {
use std::time::Duration;
use super::super::tc::NetemConfig;
let current = NetemOptions {
delay_ns: 100_000_000,
jitter_ns: 10_000_000, ..Default::default()
};
let new_config = NetemConfig::new().delay(Duration::from_millis(100)).build();
assert!(current.requires_recreation_for(&new_config));
let new_config = NetemConfig::new()
.delay(Duration::from_millis(100))
.jitter(Duration::from_millis(5))
.build();
assert!(!current.requires_recreation_for(&new_config));
}
#[test]
fn test_requires_recreation_removing_rate() {
use std::time::Duration;
use super::super::tc::NetemConfig;
let current = NetemOptions {
delay_ns: 100_000_000,
rate: 1_000_000, ..Default::default()
};
let new_config = NetemConfig::new().delay(Duration::from_millis(100)).build();
assert!(current.requires_recreation_for(&new_config));
let new_config = NetemConfig::new()
.delay(Duration::from_millis(100))
.rate(crate::util::Rate::bytes_per_sec(500_000))
.build();
assert!(!current.requires_recreation_for(&new_config));
}
#[test]
fn test_requires_recreation_removing_duplicate() {
use super::super::tc::NetemConfig;
let current = NetemOptions {
duplicate_percent: 1.0,
..Default::default()
};
let new_config = NetemConfig::new().build();
assert!(current.requires_recreation_for(&new_config));
let new_config = NetemConfig::new()
.duplicate(crate::util::Percent::new(0.5))
.build();
assert!(!current.requires_recreation_for(&new_config));
}
#[test]
fn test_requires_recreation_removing_reorder() {
use std::time::Duration;
use super::super::tc::NetemConfig;
let current = NetemOptions {
delay_ns: 100_000_000,
reorder_percent: 5.0,
gap: 5,
..Default::default()
};
let new_config = NetemConfig::new().delay(Duration::from_millis(100)).build();
assert!(current.requires_recreation_for(&new_config));
let new_config = NetemConfig::new()
.delay(Duration::from_millis(100))
.reorder(crate::util::Percent::new(2.0))
.gap(3)
.build();
assert!(!current.requires_recreation_for(&new_config));
}
#[test]
fn test_requires_recreation_removing_corrupt() {
use super::super::tc::NetemConfig;
let current = NetemOptions {
corrupt_percent: 0.1,
..Default::default()
};
let new_config = NetemConfig::new().build();
assert!(current.requires_recreation_for(&new_config));
let new_config = NetemConfig::new()
.corrupt(crate::util::Percent::new(0.05))
.build();
assert!(!current.requires_recreation_for(&new_config));
}
#[test]
fn test_requires_recreation_no_current_params() {
use std::time::Duration;
use super::super::tc::NetemConfig;
let current = NetemOptions::default();
let new_config = NetemConfig::new()
.delay(Duration::from_millis(100))
.loss(crate::util::Percent::new(1.0))
.build();
assert!(!current.requires_recreation_for(&new_config));
}
#[test]
fn test_requires_recreation_multiple_params() {
use std::time::Duration;
use super::super::tc::NetemConfig;
let current = NetemOptions {
delay_ns: 100_000_000,
jitter_ns: 10_000_000,
loss_percent: 1.0,
rate: 1_000_000,
..Default::default()
};
let new_config = NetemConfig::new()
.delay(Duration::from_millis(100))
.jitter(Duration::from_millis(10))
.loss(crate::util::Percent::new(1.0))
.build();
assert!(current.requires_recreation_for(&new_config));
let new_config = NetemConfig::new()
.delay(Duration::from_millis(50))
.jitter(Duration::from_millis(5))
.loss(crate::util::Percent::new(0.5))
.rate(crate::util::Rate::bytes_per_sec(500_000))
.build();
assert!(!current.requires_recreation_for(&new_config));
}
#[test]
fn test_requires_recreation_with_loss_model() {
use super::super::tc::NetemConfig;
let current = NetemOptions {
loss_model: Some(NetemLossModel::GilbertElliot {
p: 1.0,
r: 10.0,
h: 50.0,
k1: 0.0,
}),
..Default::default()
};
let new_config = NetemConfig::new().build();
assert!(current.requires_recreation_for(&new_config));
let new_config = NetemConfig::new()
.loss(crate::util::Percent::new(1.0))
.build();
assert!(!current.requires_recreation_for(&new_config));
}
#[test]
fn test_fq_pie_options_default() {
let opts = FqPieOptions::default();
assert_eq!(opts.limit, 0);
assert_eq!(opts.flows, 0);
assert!(opts.target().is_none());
assert!(opts.tupdate().is_none());
assert!(opts.ecn_prob().is_none());
assert!(!opts.ecn);
}
#[test]
fn test_fq_pie_options_target_accessor() {
let opts = FqPieOptions {
target_us: 15_000,
..Default::default()
};
assert_eq!(opts.target(), Some(std::time::Duration::from_millis(15)));
}
#[test]
fn test_fq_pie_options_ecn_prob_accessor() {
let opts = FqPieOptions {
ecn_prob_permille: 200, ..Default::default()
};
let p = opts.ecn_prob().unwrap();
assert!((p.as_percent() - 20.0).abs() < 1e-6);
}
#[test]
fn test_fq_pie_round_trip_via_writer() {
use std::time::Duration;
use super::super::tc::FqPieConfig;
use crate::netlink::builder::MessageBuilder;
use crate::netlink::tc::QdiscConfig;
let cfg = FqPieConfig::new()
.limit(10240)
.flows(2048)
.target(Duration::from_millis(15))
.tupdate(Duration::from_millis(20))
.alpha(2)
.beta(20)
.ecn(true)
.build();
let mut builder = MessageBuilder::new(0, 0);
let start = builder.len();
cfg.write_options(&mut builder).unwrap();
let end = builder.len();
let blob = builder.as_bytes()[start..end].to_vec();
let opts = parse_fq_pie_options(&blob);
assert_eq!(opts.limit, 10240);
assert_eq!(opts.flows, 2048);
assert_eq!(opts.target_us, 15_000);
assert_eq!(opts.tupdate_us, 20_000);
assert_eq!(opts.alpha, 2);
assert_eq!(opts.beta, 20);
assert!(opts.ecn);
}
#[test]
fn test_cake_options_default() {
let opts = CakeOptions::default();
assert!(opts.bandwidth().is_none());
assert!(opts.rtt().is_none());
assert!(opts.target().is_none());
assert_eq!(opts.diffserv_mode, 0);
assert!(!opts.nat);
}
#[test]
fn test_cake_options_bandwidth_accessor() {
use crate::util::Rate;
let opts = CakeOptions {
bandwidth_bps: 12_500_000, ..Default::default()
};
assert_eq!(opts.bandwidth(), Some(Rate::mbit(100)));
}
#[test]
fn test_cake_round_trip_via_writer() {
use std::time::Duration;
use super::super::tc::{CakeConfig, CakeDiffserv, CakeFlowMode};
use crate::netlink::builder::MessageBuilder;
use crate::netlink::tc::QdiscConfig;
use crate::util::Rate;
let cfg = CakeConfig::new()
.bandwidth(Rate::mbit(100))
.rtt(Duration::from_millis(80))
.target(Duration::from_millis(5))
.flow_mode(CakeFlowMode::Triple)
.diffserv_mode(CakeDiffserv::Diffserv4)
.nat(true)
.wash(true)
.build();
let mut builder = MessageBuilder::new(0, 0);
let start = builder.len();
cfg.write_options(&mut builder).unwrap();
let end = builder.len();
let blob = builder.as_bytes()[start..end].to_vec();
let opts = parse_cake_options(&blob);
assert_eq!(opts.bandwidth_bps, 12_500_000);
assert_eq!(opts.rtt_us, 80_000);
assert_eq!(opts.target_us, 5_000);
assert_eq!(
opts.flow_mode,
super::super::types::tc::qdisc::cake::CAKE_FLOW_TRIPLE,
);
assert_eq!(
opts.diffserv_mode,
super::super::types::tc::qdisc::cake::CAKE_DIFFSERV_DIFFSERV4,
);
assert!(opts.nat);
assert!(opts.wash);
assert_eq!(opts.bandwidth(), Some(Rate::mbit(100)));
}
#[test]
fn test_cake_unlimited_writes_zero_rate() {
use super::super::tc::CakeConfig;
use crate::netlink::builder::MessageBuilder;
use crate::netlink::tc::QdiscConfig;
let cfg = CakeConfig::new().unlimited().build();
let mut builder = MessageBuilder::new(0, 0);
let start = builder.len();
cfg.write_options(&mut builder).unwrap();
let blob = builder.as_bytes()[start..].to_vec();
let opts = parse_cake_options(&blob);
assert_eq!(opts.bandwidth_bps, 0);
assert!(opts.bandwidth().is_none());
}
}