use std::time::Duration;
pub use super::types::tc::qdisc::hfsc::TcServiceCurve;
pub use super::types::tc::qdisc::taprio::TaprioSchedEntry;
use super::{
Connection,
builder::MessageBuilder,
connection::{ack_request, create_request, replace_request},
error::{Error, Result},
interface_ref::InterfaceRef,
message::NlMsgType,
protocol::Route,
tc_handle::TcHandle,
types::tc::{
TcMsg, TcaAttr,
qdisc::{TcRateSpec, fq_codel, htb, netem::*, prio, sfq, tbf},
},
};
pub trait QdiscConfig: Send + Sync {
fn kind(&self) -> &'static str;
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()>;
fn default_handle(&self) -> Option<u32> {
None
}
}
#[derive(Debug, Clone, Default)]
pub struct NetemConfig {
pub delay: Option<Duration>,
pub jitter: Option<Duration>,
pub delay_correlation: crate::util::Percent,
pub loss: crate::util::Percent,
pub loss_correlation: crate::util::Percent,
pub duplicate: crate::util::Percent,
pub duplicate_correlation: crate::util::Percent,
pub corrupt: crate::util::Percent,
pub corrupt_correlation: crate::util::Percent,
pub reorder: crate::util::Percent,
pub reorder_correlation: crate::util::Percent,
pub gap: u32,
pub rate: Option<crate::util::Rate>,
pub limit: u32,
}
impl NetemConfig {
pub fn new() -> Self {
Self {
limit: 1000, ..Default::default()
}
}
pub fn delay(mut self, delay: Duration) -> Self {
self.delay = Some(delay);
self
}
pub fn jitter(mut self, jitter: Duration) -> Self {
self.jitter = Some(jitter);
self
}
pub fn delay_correlation(mut self, corr: crate::util::Percent) -> Self {
self.delay_correlation = corr;
self
}
pub fn loss(mut self, percent: crate::util::Percent) -> Self {
self.loss = percent;
self
}
pub fn loss_correlation(mut self, corr: crate::util::Percent) -> Self {
self.loss_correlation = corr;
self
}
pub fn duplicate(mut self, percent: crate::util::Percent) -> Self {
self.duplicate = percent;
self
}
pub fn duplicate_correlation(mut self, corr: crate::util::Percent) -> Self {
self.duplicate_correlation = corr;
self
}
pub fn corrupt(mut self, percent: crate::util::Percent) -> Self {
self.corrupt = percent;
self
}
pub fn corrupt_correlation(mut self, corr: crate::util::Percent) -> Self {
self.corrupt_correlation = corr;
self
}
pub fn reorder(mut self, percent: crate::util::Percent) -> Self {
self.reorder = percent;
self
}
pub fn reorder_correlation(mut self, corr: crate::util::Percent) -> Self {
self.reorder_correlation = corr;
self
}
pub fn gap(mut self, gap: u32) -> Self {
self.gap = gap;
self
}
pub fn rate(mut self, rate: crate::util::Rate) -> Self {
self.rate = Some(rate);
self
}
pub fn limit(mut self, packets: u32) -> Self {
self.limit = packets;
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for NetemConfig {
fn kind(&self) -> &'static str {
"netem"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
if !self.reorder.is_zero() && self.delay.is_none() {
return Err(Error::InvalidMessage(
"netem: reorder requires delay to be set".into(),
));
}
let mut qopt = TcNetemQopt::new();
qopt.limit = self.limit;
if let Some(delay) = self.delay {
qopt.latency = delay.as_micros() as u32;
}
if let Some(jitter) = self.jitter {
qopt.jitter = jitter.as_micros() as u32;
}
if !self.loss.is_zero() {
qopt.loss = self.loss.as_kernel_probability();
}
if !self.duplicate.is_zero() {
qopt.duplicate = self.duplicate.as_kernel_probability();
}
if !self.reorder.is_zero() && self.gap == 0 {
qopt.gap = 1; } else {
qopt.gap = self.gap;
}
builder.append(&qopt);
if let Some(delay) = self.delay {
let latency_ns = delay.as_nanos() as i64;
builder.append_attr(TCA_NETEM_LATENCY64, &latency_ns.to_ne_bytes());
}
if let Some(jitter) = self.jitter {
let jitter_ns = jitter.as_nanos() as i64;
builder.append_attr(TCA_NETEM_JITTER64, &jitter_ns.to_ne_bytes());
}
if !self.delay_correlation.is_zero()
|| !self.loss_correlation.is_zero()
|| !self.duplicate_correlation.is_zero()
{
let corr = TcNetemCorr {
delay_corr: self.delay_correlation.as_kernel_probability(),
loss_corr: self.loss_correlation.as_kernel_probability(),
dup_corr: self.duplicate_correlation.as_kernel_probability(),
};
builder.append_attr(TCA_NETEM_CORR, corr.as_bytes());
}
if !self.reorder.is_zero() {
let reorder = TcNetemReorder {
probability: self.reorder.as_kernel_probability(),
correlation: self.reorder_correlation.as_kernel_probability(),
};
builder.append_attr(TCA_NETEM_REORDER, reorder.as_bytes());
}
if !self.corrupt.is_zero() {
let corrupt = TcNetemCorrupt {
probability: self.corrupt.as_kernel_probability(),
correlation: self.corrupt_correlation.as_kernel_probability(),
};
builder.append_attr(TCA_NETEM_CORRUPT, corrupt.as_bytes());
}
if let Some(rate) = self.rate {
let bytes_per_sec = rate.as_bytes_per_sec();
let mut rate_struct = TcNetemRate::default();
if bytes_per_sec > u32::MAX as u64 {
rate_struct.rate = u32::MAX;
builder.append_attr(TCA_NETEM_RATE, rate_struct.as_bytes());
builder.append_attr(TCA_NETEM_RATE64, &bytes_per_sec.to_ne_bytes());
} else {
rate_struct.rate = bytes_per_sec as u32;
builder.append_attr(TCA_NETEM_RATE, rate_struct.as_bytes());
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct FqCodelConfig {
pub target: Option<Duration>,
pub interval: Option<Duration>,
pub limit: Option<u32>,
pub flows: Option<u32>,
pub quantum: Option<u32>,
pub ecn: bool,
pub ce_threshold: Option<Duration>,
pub memory_limit: Option<u32>,
}
impl Default for FqCodelConfig {
fn default() -> Self {
Self::new()
}
}
impl FqCodelConfig {
pub fn new() -> Self {
Self {
target: None,
interval: None,
limit: None,
flows: None,
quantum: None,
ecn: false,
ce_threshold: None,
memory_limit: None,
}
}
pub fn target(mut self, target: Duration) -> Self {
self.target = Some(target);
self
}
pub fn interval(mut self, interval: Duration) -> Self {
self.interval = Some(interval);
self
}
pub fn limit(mut self, packets: u32) -> Self {
self.limit = Some(packets);
self
}
pub fn flows(mut self, flows: u32) -> Self {
self.flows = Some(flows);
self
}
pub fn quantum(mut self, bytes: u32) -> Self {
self.quantum = Some(bytes);
self
}
pub fn ecn(mut self, enable: bool) -> Self {
self.ecn = enable;
self
}
pub fn ce_threshold(mut self, threshold: Duration) -> Self {
self.ce_threshold = Some(threshold);
self
}
pub fn memory_limit(mut self, bytes: u32) -> Self {
self.memory_limit = Some(bytes);
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for FqCodelConfig {
fn kind(&self) -> &'static str {
"fq_codel"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
if let Some(target) = self.target {
builder.append_attr_u32(fq_codel::TCA_FQ_CODEL_TARGET, target.as_micros() as u32);
}
if let Some(interval) = self.interval {
builder.append_attr_u32(fq_codel::TCA_FQ_CODEL_INTERVAL, interval.as_micros() as u32);
}
if let Some(limit) = self.limit {
builder.append_attr_u32(fq_codel::TCA_FQ_CODEL_LIMIT, limit);
}
if let Some(flows) = self.flows {
builder.append_attr_u32(fq_codel::TCA_FQ_CODEL_FLOWS, flows);
}
if let Some(quantum) = self.quantum {
builder.append_attr_u32(fq_codel::TCA_FQ_CODEL_QUANTUM, quantum);
}
if self.ecn {
builder.append_attr_u32(fq_codel::TCA_FQ_CODEL_ECN, 1);
}
if let Some(ce) = self.ce_threshold {
builder.append_attr_u32(fq_codel::TCA_FQ_CODEL_CE_THRESHOLD, ce.as_micros() as u32);
}
if let Some(mem) = self.memory_limit {
builder.append_attr_u32(fq_codel::TCA_FQ_CODEL_MEMORY_LIMIT, mem);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TbfConfig {
pub rate: crate::util::Rate,
pub peakrate: Option<crate::util::Rate>,
pub burst: crate::util::Bytes,
pub mtu: u32,
pub limit: crate::util::Bytes,
}
impl Default for TbfConfig {
fn default() -> Self {
Self::new()
}
}
impl TbfConfig {
pub fn new() -> Self {
Self {
rate: crate::util::Rate::ZERO,
peakrate: None,
burst: crate::util::Bytes::ZERO,
mtu: 1514,
limit: crate::util::Bytes::ZERO,
}
}
pub fn rate(mut self, rate: crate::util::Rate) -> Self {
self.rate = rate;
self
}
pub fn peakrate(mut self, rate: crate::util::Rate) -> Self {
self.peakrate = Some(rate);
self
}
pub fn burst(mut self, b: crate::util::Bytes) -> Self {
self.burst = b;
self
}
pub fn mtu(mut self, mtu: u32) -> Self {
self.mtu = mtu;
self
}
pub fn limit(mut self, b: crate::util::Bytes) -> Self {
self.limit = b;
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for TbfConfig {
fn kind(&self) -> &'static str {
"tbf"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
let rate_bps = self.rate.as_bytes_per_sec();
let peakrate_bps = self.peakrate.map(|p| p.as_bytes_per_sec());
let qopt = tbf::TcTbfQopt {
rate: TcRateSpec::new(rate_bps.min(u32::MAX as u64) as u32),
peakrate: peakrate_bps
.map(|pr| TcRateSpec::new(pr.min(u32::MAX as u64) as u32))
.unwrap_or_default(),
limit: self.limit.as_u32_saturating(),
buffer: self.burst.as_u32_saturating(),
mtu: self.mtu,
};
builder.append_attr(tbf::TCA_TBF_PARMS, qopt.as_bytes());
if rate_bps > u32::MAX as u64 {
builder.append_attr(tbf::TCA_TBF_RATE64, &rate_bps.to_ne_bytes());
}
if let Some(pr) = peakrate_bps
&& pr > u32::MAX as u64
{
builder.append_attr(tbf::TCA_TBF_PRATE64, &pr.to_ne_bytes());
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct HtbQdiscConfig {
pub default_class: u32,
pub r2q: u32,
pub direct_qlen: Option<u32>,
}
impl Default for HtbQdiscConfig {
fn default() -> Self {
Self::new()
}
}
impl HtbQdiscConfig {
pub fn new() -> Self {
Self {
default_class: 0,
r2q: 10,
direct_qlen: None,
}
}
pub fn default_class(mut self, classid: u32) -> Self {
self.default_class = classid;
self
}
pub fn r2q(mut self, r2q: u32) -> Self {
self.r2q = r2q;
self
}
pub fn direct_qlen(mut self, qlen: u32) -> Self {
self.direct_qlen = Some(qlen);
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for HtbQdiscConfig {
fn kind(&self) -> &'static str {
"htb"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
let glob = htb::TcHtbGlob::new().with_default(self.default_class);
builder.append_attr(htb::TCA_HTB_INIT, glob.as_bytes());
if let Some(qlen) = self.direct_qlen {
builder.append_attr_u32(htb::TCA_HTB_DIRECT_QLEN, qlen);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PrioConfig {
pub bands: i32,
pub priomap: [u8; 16],
}
impl Default for PrioConfig {
fn default() -> Self {
Self::new()
}
}
impl PrioConfig {
pub fn new() -> Self {
Self {
bands: 3,
priomap: [1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
}
}
pub fn bands(mut self, bands: i32) -> Self {
self.bands = bands;
self
}
pub fn priomap(mut self, map: [u8; 16]) -> Self {
self.priomap = map;
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for PrioConfig {
fn kind(&self) -> &'static str {
"prio"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
let qopt = prio::TcPrioQopt {
bands: self.bands,
priomap: self.priomap,
};
builder.append(&qopt);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct SfqConfig {
pub perturb: i32,
pub limit: u32,
pub quantum: u32,
}
impl Default for SfqConfig {
fn default() -> Self {
Self::new()
}
}
impl SfqConfig {
pub fn new() -> Self {
Self {
perturb: 0,
limit: 127,
quantum: 0,
}
}
pub fn perturb(mut self, seconds: i32) -> Self {
self.perturb = seconds;
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.limit = limit;
self
}
pub fn quantum(mut self, bytes: u32) -> Self {
self.quantum = bytes;
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for SfqConfig {
fn kind(&self) -> &'static str {
"sfq"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
let qopt = sfq::TcSfqQopt {
quantum: self.quantum,
perturb_period: self.perturb,
limit: self.limit,
divisor: 0,
flows: 0,
};
builder.append(&qopt);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct RedConfig {
pub limit: u32,
pub min: u32,
pub max: u32,
pub max_p: u8,
pub ecn: bool,
pub harddrop: bool,
pub adaptive: bool,
}
impl Default for RedConfig {
fn default() -> Self {
Self::new()
}
}
impl RedConfig {
pub fn new() -> Self {
Self {
limit: 0,
min: 0,
max: 0,
max_p: 5, ecn: false,
harddrop: false,
adaptive: false,
}
}
pub fn limit(mut self, bytes: u32) -> Self {
self.limit = bytes;
self
}
pub fn min(mut self, bytes: u32) -> Self {
self.min = bytes;
self
}
pub fn max(mut self, bytes: u32) -> Self {
self.max = bytes;
self
}
pub fn max_probability(mut self, percent: f64) -> Self {
self.max_p = ((percent / 100.0) * 255.0).clamp(0.0, 255.0) as u8;
self
}
pub fn ecn(mut self, enable: bool) -> Self {
self.ecn = enable;
self
}
pub fn harddrop(mut self, enable: bool) -> Self {
self.harddrop = enable;
self
}
pub fn adaptive(mut self, enable: bool) -> Self {
self.adaptive = enable;
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for RedConfig {
fn kind(&self) -> &'static str {
"red"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::red;
let mut flags: u8 = 0;
if self.ecn {
flags |= red::TC_RED_ECN as u8;
}
if self.harddrop {
flags |= red::TC_RED_HARDDROP as u8;
}
if self.adaptive {
flags |= red::TC_RED_ADAPTATIVE as u8;
}
let qopt = red::TcRedQopt {
limit: self.limit,
qth_min: self.min,
qth_max: self.max,
wlog: 9, plog: 13, scell_log: 0, flags,
};
builder.append_attr(red::TCA_RED_PARMS, qopt.as_bytes());
let max_p = (self.max_p as u32) << 24;
builder.append_attr_u32(red::TCA_RED_MAX_P, max_p);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PieConfig {
pub target: Option<Duration>,
pub limit: Option<u32>,
pub tupdate: Option<Duration>,
pub alpha: Option<u32>,
pub beta: Option<u32>,
pub ecn: bool,
pub bytemode: bool,
}
impl Default for PieConfig {
fn default() -> Self {
Self::new()
}
}
impl PieConfig {
pub fn new() -> Self {
Self {
target: None,
limit: None,
tupdate: None,
alpha: None,
beta: None,
ecn: false,
bytemode: false,
}
}
pub fn target(mut self, target: Duration) -> Self {
self.target = Some(target);
self
}
pub fn limit(mut self, packets: u32) -> Self {
self.limit = Some(packets);
self
}
pub fn tupdate(mut self, interval: Duration) -> Self {
self.tupdate = Some(interval);
self
}
pub fn alpha(mut self, alpha: u32) -> Self {
self.alpha = Some(alpha);
self
}
pub fn beta(mut self, beta: u32) -> Self {
self.beta = Some(beta);
self
}
pub fn ecn(mut self, enable: bool) -> Self {
self.ecn = enable;
self
}
pub fn bytemode(mut self, enable: bool) -> Self {
self.bytemode = enable;
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for PieConfig {
fn kind(&self) -> &'static str {
"pie"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::pie;
if let Some(target) = self.target {
builder.append_attr_u32(pie::TCA_PIE_TARGET, target.as_micros() as u32);
}
if let Some(limit) = self.limit {
builder.append_attr_u32(pie::TCA_PIE_LIMIT, limit);
}
if let Some(tupdate) = self.tupdate {
builder.append_attr_u32(pie::TCA_PIE_TUPDATE, tupdate.as_micros() as u32);
}
if let Some(alpha) = self.alpha {
builder.append_attr_u32(pie::TCA_PIE_ALPHA, alpha);
}
if let Some(beta) = self.beta {
builder.append_attr_u32(pie::TCA_PIE_BETA, beta);
}
if self.ecn {
builder.append_attr_u32(pie::TCA_PIE_ECN, 1);
}
if self.bytemode {
builder.append_attr_u32(pie::TCA_PIE_BYTEMODE, 1);
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct IngressConfig;
impl IngressConfig {
pub fn new() -> Self {
Self
}
}
impl QdiscConfig for IngressConfig {
fn kind(&self) -> &'static str {
"ingress"
}
fn write_options(&self, _builder: &mut MessageBuilder) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct ClsactConfig;
impl ClsactConfig {
pub fn new() -> Self {
Self
}
}
impl QdiscConfig for ClsactConfig {
fn kind(&self) -> &'static str {
"clsact"
}
fn write_options(&self, _builder: &mut MessageBuilder) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PfifoConfig {
pub limit: u32,
}
impl Default for PfifoConfig {
fn default() -> Self {
Self::new()
}
}
impl PfifoConfig {
pub fn new() -> Self {
Self { limit: 1000 }
}
pub fn limit(mut self, packets: u32) -> Self {
self.limit = packets;
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for PfifoConfig {
fn kind(&self) -> &'static str {
"pfifo"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::fifo::TcFifoQopt;
let qopt = TcFifoQopt::new(self.limit);
builder.append(&qopt);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct BfifoConfig {
pub limit: u32,
}
impl Default for BfifoConfig {
fn default() -> Self {
Self::new()
}
}
impl BfifoConfig {
pub fn new() -> Self {
Self {
limit: 100 * 1024, }
}
pub fn limit(mut self, bytes: u32) -> Self {
self.limit = bytes;
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for BfifoConfig {
fn kind(&self) -> &'static str {
"bfifo"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::fifo::TcFifoQopt;
let qopt = TcFifoQopt::new(self.limit);
builder.append(&qopt);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct DrrConfig {}
impl Default for DrrConfig {
fn default() -> Self {
Self::new()
}
}
impl DrrConfig {
pub fn new() -> Self {
Self {}
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for DrrConfig {
fn kind(&self) -> &'static str {
"drr"
}
fn write_options(&self, _builder: &mut MessageBuilder) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct QfqConfig {}
impl Default for QfqConfig {
fn default() -> Self {
Self::new()
}
}
impl QfqConfig {
pub fn new() -> Self {
Self {}
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for QfqConfig {
fn kind(&self) -> &'static str {
"qfq"
}
fn write_options(&self, _builder: &mut MessageBuilder) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PlugConfig {
pub limit: Option<u32>,
}
impl Default for PlugConfig {
fn default() -> Self {
Self::new()
}
}
impl PlugConfig {
pub fn new() -> Self {
Self { limit: None }
}
pub fn limit(mut self, bytes: u32) -> Self {
self.limit = Some(bytes);
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for PlugConfig {
fn kind(&self) -> &'static str {
"plug"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::plug::TcPlugQopt;
if let Some(limit) = self.limit {
let qopt = TcPlugQopt::limit(limit);
builder.append(&qopt);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct MqprioConfig {
pub num_tc: u8,
pub prio_tc_map: [u8; 16],
pub hw: bool,
pub count: [u16; 16],
pub offset: [u16; 16],
}
impl Default for MqprioConfig {
fn default() -> Self {
Self::new()
}
}
impl MqprioConfig {
pub fn new() -> Self {
Self {
num_tc: 8,
prio_tc_map: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 1, 1, 3, 3, 3, 3],
hw: true,
count: [0; 16],
offset: [0; 16],
}
}
pub fn num_tc(mut self, num_tc: u8) -> Self {
self.num_tc = num_tc.min(16);
self
}
pub fn map(mut self, map: &[u8]) -> Self {
for (i, &tc) in map.iter().enumerate().take(16) {
self.prio_tc_map[i] = tc;
}
self
}
pub fn hw_offload(mut self, enable: bool) -> Self {
self.hw = enable;
self
}
pub fn queues(mut self, queues: &[(u16, u16)]) -> Self {
for (i, &(c, o)) in queues.iter().enumerate().take(16) {
self.count[i] = c;
self.offset[i] = o;
}
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for MqprioConfig {
fn kind(&self) -> &'static str {
"mqprio"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::mqprio::TcMqprioQopt;
let mut qopt = TcMqprioQopt::new()
.with_num_tc(self.num_tc)
.with_hw(self.hw);
qopt.prio_tc_map = self.prio_tc_map;
qopt.count = self.count;
qopt.offset = self.offset;
builder.append(&qopt);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TaprioConfig {
pub num_tc: u8,
pub prio_tc_map: [u8; 16],
pub count: [u16; 16],
pub offset: [u16; 16],
pub clockid: i32,
pub base_time: i64,
pub cycle_time: i64,
pub cycle_time_extension: i64,
pub entries: Vec<super::types::tc::qdisc::taprio::TaprioSchedEntry>,
pub flags: u32,
pub txtime_delay: u32,
}
impl Default for TaprioConfig {
fn default() -> Self {
Self::new()
}
}
impl TaprioConfig {
pub fn new() -> Self {
Self {
num_tc: 0,
prio_tc_map: [0; 16],
count: [0; 16],
offset: [0; 16],
clockid: -1,
base_time: 0,
cycle_time: 0,
cycle_time_extension: 0,
entries: Vec::new(),
flags: 0,
txtime_delay: 0,
}
}
pub fn num_tc(mut self, num_tc: u8) -> Self {
self.num_tc = num_tc.min(16);
self
}
pub fn map(mut self, map: &[u8]) -> Self {
for (i, &tc) in map.iter().enumerate().take(16) {
self.prio_tc_map[i] = tc;
}
self
}
pub fn queues(mut self, queues: &[(u16, u16)]) -> Self {
for (i, &(c, o)) in queues.iter().enumerate().take(16) {
self.count[i] = c;
self.offset[i] = o;
}
self
}
pub fn clockid(mut self, clockid: i32) -> Self {
self.clockid = clockid;
self
}
pub fn base_time(mut self, base_time: i64) -> Self {
self.base_time = base_time;
self
}
pub fn cycle_time(mut self, cycle_time: i64) -> Self {
self.cycle_time = cycle_time;
self
}
pub fn cycle_time_extension(mut self, extension: i64) -> Self {
self.cycle_time_extension = extension;
self
}
pub fn entry(mut self, entry: super::types::tc::qdisc::taprio::TaprioSchedEntry) -> Self {
self.entries.push(entry);
self
}
pub fn txtime_assist(mut self, enable: bool) -> Self {
use super::types::tc::qdisc::taprio::TAPRIO_ATTR_FLAG_TXTIME_ASSIST;
if enable {
self.flags |= TAPRIO_ATTR_FLAG_TXTIME_ASSIST;
} else {
self.flags &= !TAPRIO_ATTR_FLAG_TXTIME_ASSIST;
}
self
}
pub fn full_offload(mut self, enable: bool) -> Self {
use super::types::tc::qdisc::taprio::TAPRIO_ATTR_FLAG_FULL_OFFLOAD;
if enable {
self.flags |= TAPRIO_ATTR_FLAG_FULL_OFFLOAD;
} else {
self.flags &= !TAPRIO_ATTR_FLAG_FULL_OFFLOAD;
}
self
}
pub fn txtime_delay(mut self, delay: u32) -> Self {
self.txtime_delay = delay;
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for TaprioConfig {
fn kind(&self) -> &'static str {
"taprio"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::{mqprio::TcMqprioQopt, taprio::*};
let mut qopt = TcMqprioQopt::new().with_num_tc(self.num_tc).with_hw(false);
qopt.prio_tc_map = self.prio_tc_map;
qopt.count = self.count;
qopt.offset = self.offset;
builder.append_attr(TCA_TAPRIO_ATTR_PRIOMAP, qopt.as_bytes());
if self.clockid >= 0 {
builder.append_attr(TCA_TAPRIO_ATTR_SCHED_CLOCKID, &self.clockid.to_ne_bytes());
}
if self.base_time != 0 {
builder.append_attr(
TCA_TAPRIO_ATTR_SCHED_BASE_TIME,
&self.base_time.to_ne_bytes(),
);
}
if self.cycle_time != 0 {
builder.append_attr(
TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME,
&self.cycle_time.to_ne_bytes(),
);
}
if self.cycle_time_extension != 0 {
builder.append_attr(
TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION,
&self.cycle_time_extension.to_ne_bytes(),
);
}
if self.flags != 0 {
builder.append_attr(TCA_TAPRIO_ATTR_FLAGS, &self.flags.to_ne_bytes());
}
if self.txtime_delay != 0 {
builder.append_attr(
TCA_TAPRIO_ATTR_TXTIME_DELAY,
&self.txtime_delay.to_ne_bytes(),
);
}
if !self.entries.is_empty() {
let list_token = builder.nest_start(TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST);
for (idx, entry) in self.entries.iter().enumerate() {
let entry_token = builder.nest_start(TCA_TAPRIO_ATTR_SCHED_SINGLE_ENTRY);
builder.append_attr(TCA_TAPRIO_SCHED_ENTRY_INDEX, &(idx as u32).to_ne_bytes());
builder.append_attr(TCA_TAPRIO_SCHED_ENTRY_CMD, &[entry.cmd]);
builder.append_attr(
TCA_TAPRIO_SCHED_ENTRY_GATE_MASK,
&entry.gate_mask.to_ne_bytes(),
);
builder.append_attr(
TCA_TAPRIO_SCHED_ENTRY_INTERVAL,
&entry.interval.to_ne_bytes(),
);
builder.nest_end(entry_token);
}
builder.nest_end(list_token);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct HfscConfig {
pub default_class: u16,
}
impl Default for HfscConfig {
fn default() -> Self {
Self::new()
}
}
impl HfscConfig {
pub fn new() -> Self {
Self { default_class: 0 }
}
pub fn default_class(mut self, classid: u16) -> Self {
self.default_class = classid;
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for HfscConfig {
fn kind(&self) -> &'static str {
"hfsc"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::hfsc::TcHfscQopt;
let qopt = TcHfscQopt::new(self.default_class);
builder.append(&qopt);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct EtfConfig {
pub delta: i32,
pub clockid: i32,
pub deadline_mode: bool,
pub offload: bool,
pub skip_sock_check: bool,
}
impl Default for EtfConfig {
fn default() -> Self {
Self::new()
}
}
impl EtfConfig {
pub fn new() -> Self {
Self {
delta: 0,
clockid: -1, deadline_mode: false,
offload: false,
skip_sock_check: false,
}
}
pub fn clockid(mut self, clockid: i32) -> Self {
self.clockid = clockid;
self
}
pub fn delta_ns(mut self, delta: i32) -> Self {
self.delta = delta;
self
}
pub fn deadline_mode(mut self, enable: bool) -> Self {
self.deadline_mode = enable;
self
}
pub fn offload(mut self, enable: bool) -> Self {
self.offload = enable;
self
}
pub fn skip_sock_check(mut self, enable: bool) -> Self {
self.skip_sock_check = enable;
self
}
pub fn build(self) -> Self {
self
}
}
impl QdiscConfig for EtfConfig {
fn kind(&self) -> &'static str {
"etf"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::etf::{
TC_ETF_DEADLINE_MODE_ON, TC_ETF_OFFLOAD_ON, TC_ETF_SKIP_SOCK_CHECK, TCA_ETF_PARMS,
TcEtfQopt,
};
let mut flags = 0i32;
if self.deadline_mode {
flags |= TC_ETF_DEADLINE_MODE_ON;
}
if self.offload {
flags |= TC_ETF_OFFLOAD_ON;
}
if self.skip_sock_check {
flags |= TC_ETF_SKIP_SOCK_CHECK;
}
let qopt = TcEtfQopt {
delta: self.delta,
clockid: self.clockid,
flags,
};
builder.append_attr(TCA_ETF_PARMS, qopt.as_bytes());
Ok(())
}
}
pub trait ClassConfig: Send + Sync {
fn kind(&self) -> &'static str;
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()>;
}
#[derive(Debug, Clone)]
pub struct HtbClassConfig {
rate: crate::util::Rate,
ceil: Option<crate::util::Rate>,
burst: Option<crate::util::Bytes>,
cburst: Option<crate::util::Bytes>,
prio: Option<u32>,
quantum: Option<u32>,
mtu: u32,
mpu: u16,
overhead: u16,
}
impl HtbClassConfig {
pub fn new(rate: crate::util::Rate) -> Self {
Self {
rate,
ceil: None,
burst: None,
cburst: None,
prio: None,
quantum: None,
mtu: 1600,
mpu: 0,
overhead: 0,
}
}
pub fn ceil(mut self, ceil: crate::util::Rate) -> Self {
self.ceil = Some(ceil);
self
}
pub fn burst(mut self, burst: crate::util::Bytes) -> Self {
self.burst = Some(burst);
self
}
pub fn cburst(mut self, cburst: crate::util::Bytes) -> Self {
self.cburst = Some(cburst);
self
}
pub fn prio(mut self, prio: u32) -> Self {
self.prio = Some(prio.min(7));
self
}
pub fn quantum(mut self, quantum: u32) -> Self {
self.quantum = Some(quantum);
self
}
pub fn mtu(mut self, mtu: u32) -> Self {
self.mtu = mtu;
self
}
pub fn mpu(mut self, mpu: u16) -> Self {
self.mpu = mpu;
self
}
pub fn overhead(mut self, overhead: u16) -> Self {
self.overhead = overhead;
self
}
pub fn build(self) -> Self {
self
}
}
impl ClassConfig for HtbClassConfig {
fn kind(&self) -> &'static str {
"htb"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
let cfg = self;
let rate = cfg.rate.as_bytes_per_sec();
let ceil = cfg.ceil.unwrap_or(cfg.rate).as_bytes_per_sec();
let hz: u64 = 1000;
let burst = cfg
.burst
.map(|b| b.as_u32_saturating())
.unwrap_or_else(|| (rate / hz + cfg.mtu as u64) as u32);
let cburst = cfg
.cburst
.map(|b| b.as_u32_saturating())
.unwrap_or_else(|| (ceil / hz + cfg.mtu as u64) as u32);
let buffer = (burst as u64 * 1_000_000)
.checked_div(rate)
.map(|v| v as u32)
.unwrap_or(burst);
let cbuffer = (cburst as u64 * 1_000_000)
.checked_div(ceil)
.map(|v| v as u32)
.unwrap_or(cburst);
let opt = htb::TcHtbOpt {
rate: TcRateSpec {
rate: if rate >= (1u64 << 32) {
u32::MAX
} else {
rate as u32
},
mpu: cfg.mpu,
overhead: cfg.overhead,
..Default::default()
},
ceil: TcRateSpec {
rate: if ceil >= (1u64 << 32) {
u32::MAX
} else {
ceil as u32
},
mpu: cfg.mpu,
overhead: cfg.overhead,
..Default::default()
},
buffer,
cbuffer,
quantum: cfg.quantum.unwrap_or(0),
prio: cfg.prio.unwrap_or(0),
..Default::default()
};
if rate >= (1u64 << 32) {
builder.append_attr(htb::TCA_HTB_RATE64, &rate.to_ne_bytes());
}
if ceil >= (1u64 << 32) {
builder.append_attr(htb::TCA_HTB_CEIL64, &ceil.to_ne_bytes());
}
builder.append_attr(htb::TCA_HTB_PARMS, opt.as_bytes());
let rtab = compute_htb_rate_table(rate, cfg.mtu);
let ctab = compute_htb_rate_table(ceil, cfg.mtu);
builder.append_attr(htb::TCA_HTB_RTAB, &rtab);
builder.append_attr(htb::TCA_HTB_CTAB, &ctab);
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct HfscClassConfig {
rsc: Option<TcServiceCurve>,
fsc: Option<TcServiceCurve>,
usc: Option<TcServiceCurve>,
}
impl HfscClassConfig {
pub fn new() -> Self {
Self::default()
}
pub fn rt_curve(mut self, curve: TcServiceCurve) -> Self {
self.rsc = Some(curve);
self
}
pub fn rt_rate(mut self, rate: crate::util::Rate) -> Self {
self.rsc = Some(TcServiceCurve::rate(rate.as_u32_bytes_per_sec_saturating()));
self
}
pub fn ls_curve(mut self, curve: TcServiceCurve) -> Self {
self.fsc = Some(curve);
self
}
pub fn ls_rate(mut self, rate: crate::util::Rate) -> Self {
self.fsc = Some(TcServiceCurve::rate(rate.as_u32_bytes_per_sec_saturating()));
self
}
pub fn ul_curve(mut self, curve: TcServiceCurve) -> Self {
self.usc = Some(curve);
self
}
pub fn ul_rate(mut self, rate: crate::util::Rate) -> Self {
self.usc = Some(TcServiceCurve::rate(rate.as_u32_bytes_per_sec_saturating()));
self
}
pub fn build(self) -> Self {
self
}
}
impl ClassConfig for HfscClassConfig {
fn kind(&self) -> &'static str {
"hfsc"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::hfsc::{TCA_HFSC_FSC, TCA_HFSC_RSC, TCA_HFSC_USC};
let cfg = self;
if let Some(ref rsc) = cfg.rsc {
builder.append_attr(TCA_HFSC_RSC, rsc.as_bytes());
}
if let Some(ref fsc) = cfg.fsc {
builder.append_attr(TCA_HFSC_FSC, fsc.as_bytes());
}
if let Some(ref usc) = cfg.usc {
builder.append_attr(TCA_HFSC_USC, usc.as_bytes());
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct DrrClassConfig {
quantum: Option<crate::util::Bytes>,
}
impl DrrClassConfig {
pub fn new() -> Self {
Self::default()
}
pub fn quantum(mut self, q: crate::util::Bytes) -> Self {
self.quantum = Some(q);
self
}
pub fn build(self) -> Self {
self
}
}
impl ClassConfig for DrrClassConfig {
fn kind(&self) -> &'static str {
"drr"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::drr::TCA_DRR_QUANTUM;
if let Some(quantum) = self.quantum {
builder.append_attr_u32(TCA_DRR_QUANTUM, quantum.as_u32_saturating());
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct QfqClassConfig {
weight: Option<u32>,
lmax: Option<crate::util::Bytes>,
}
impl QfqClassConfig {
pub fn new() -> Self {
Self::default()
}
pub fn weight(mut self, weight: u32) -> Self {
self.weight = Some(weight.clamp(1, 1023));
self
}
pub fn lmax(mut self, b: crate::util::Bytes) -> Self {
self.lmax = Some(b);
self
}
pub fn build(self) -> Self {
self
}
}
impl ClassConfig for QfqClassConfig {
fn kind(&self) -> &'static str {
"qfq"
}
fn write_options(&self, builder: &mut MessageBuilder) -> Result<()> {
use super::types::tc::qdisc::qfq::{TCA_QFQ_LMAX, TCA_QFQ_WEIGHT};
if let Some(weight) = self.weight {
builder.append_attr_u32(TCA_QFQ_WEIGHT, weight);
}
if let Some(lmax) = self.lmax {
builder.append_attr_u32(TCA_QFQ_LMAX, lmax.as_u32_saturating());
}
Ok(())
}
}
fn add_class_options(builder: &mut MessageBuilder, kind: &str, params: &[String]) -> Result<()> {
if params.is_empty() {
return Ok(());
}
let options_token = builder.nest_start(TcaAttr::Options as u16);
match kind {
"htb" => add_htb_class_options(builder, params)?,
_ => {
}
}
builder.nest_end(options_token);
Ok(())
}
fn add_htb_class_options(builder: &mut MessageBuilder, params: &[String]) -> Result<()> {
use crate::util::parse::{get_rate, get_size};
let mut rate64: u64 = 0;
let mut ceil64: u64 = 0;
let mut burst: u32 = 0;
let mut cburst: u32 = 0;
let mut prio: u32 = 0;
let mut quantum: u32 = 0;
let mut mtu: u32 = 1600;
let mut mpu: u16 = 0;
let mut overhead: u16 = 0;
let mut i = 0;
while i < params.len() {
match params[i].as_str() {
"rate" if i + 1 < params.len() => {
rate64 = get_rate(¶ms[i + 1])
.map_err(|_| Error::InvalidMessage("invalid rate".into()))?;
i += 2;
}
"ceil" if i + 1 < params.len() => {
ceil64 = get_rate(¶ms[i + 1])
.map_err(|_| Error::InvalidMessage("invalid ceil".into()))?;
i += 2;
}
"burst" | "buffer" | "maxburst" if i + 1 < params.len() => {
burst = get_size(¶ms[i + 1])
.map_err(|_| Error::InvalidMessage("invalid burst".into()))?
as u32;
i += 2;
}
"cburst" | "cbuffer" | "cmaxburst" if i + 1 < params.len() => {
cburst = get_size(¶ms[i + 1])
.map_err(|_| Error::InvalidMessage("invalid cburst".into()))?
as u32;
i += 2;
}
"prio" if i + 1 < params.len() => {
prio = params[i + 1]
.parse()
.map_err(|_| Error::InvalidMessage("invalid prio".into()))?;
i += 2;
}
"quantum" if i + 1 < params.len() => {
quantum = get_size(¶ms[i + 1])
.map_err(|_| Error::InvalidMessage("invalid quantum".into()))?
as u32;
i += 2;
}
"mtu" if i + 1 < params.len() => {
mtu = params[i + 1]
.parse()
.map_err(|_| Error::InvalidMessage("invalid mtu".into()))?;
i += 2;
}
"mpu" if i + 1 < params.len() => {
mpu = params[i + 1]
.parse()
.map_err(|_| Error::InvalidMessage("invalid mpu".into()))?;
i += 2;
}
"overhead" if i + 1 < params.len() => {
overhead = params[i + 1]
.parse()
.map_err(|_| Error::InvalidMessage("invalid overhead".into()))?;
i += 2;
}
_ => i += 1,
}
}
if rate64 == 0 {
return Err(Error::InvalidMessage("htb class: rate is required".into()));
}
if ceil64 == 0 {
ceil64 = rate64;
}
let hz: u64 = 1000;
if burst == 0 {
burst = (rate64 / hz + mtu as u64) as u32;
}
if cburst == 0 {
cburst = (ceil64 / hz + mtu as u64) as u32;
}
let buffer = (burst as u64 * 1_000_000)
.checked_div(rate64)
.map(|v| v as u32)
.unwrap_or(burst);
let cbuffer = (cburst as u64 * 1_000_000)
.checked_div(ceil64)
.map(|v| v as u32)
.unwrap_or(cburst);
let opt = htb::TcHtbOpt {
rate: TcRateSpec {
rate: if rate64 >= (1u64 << 32) {
u32::MAX
} else {
rate64 as u32
},
mpu,
overhead,
..Default::default()
},
ceil: TcRateSpec {
rate: if ceil64 >= (1u64 << 32) {
u32::MAX
} else {
ceil64 as u32
},
mpu,
overhead,
..Default::default()
},
buffer,
cbuffer,
quantum,
prio,
..Default::default()
};
if rate64 >= (1u64 << 32) {
builder.append_attr(htb::TCA_HTB_RATE64, &rate64.to_ne_bytes());
}
if ceil64 >= (1u64 << 32) {
builder.append_attr(htb::TCA_HTB_CEIL64, &ceil64.to_ne_bytes());
}
builder.append_attr(htb::TCA_HTB_PARMS, opt.as_bytes());
let rtab = compute_htb_rate_table(rate64, mtu);
let ctab = compute_htb_rate_table(ceil64, mtu);
builder.append_attr(htb::TCA_HTB_RTAB, &rtab);
builder.append_attr(htb::TCA_HTB_CTAB, &ctab);
Ok(())
}
fn compute_htb_rate_table(rate: u64, mtu: u32) -> [u8; 1024] {
let mut table = [0u8; 1024];
if rate == 0 {
return table;
}
let cell_log: u32 = 3;
let cell_size = 1u32 << cell_log;
let time_units_per_sec: u64 = 1_000_000;
for i in 0..256 {
let size = ((i + 1) as u32) * cell_size;
let size = size.min(mtu);
let time = (size as u64 * time_units_per_sec) / rate;
let time = time.min(u32::MAX as u64) as u32;
let offset = i * 4;
table[offset..offset + 4].copy_from_slice(&time.to_ne_bytes());
}
table
}
impl Connection<Route> {
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_qdisc"))]
pub async fn add_qdisc(
&self,
dev: impl Into<InterfaceRef>,
config: impl QdiscConfig,
) -> Result<()> {
self.add_qdisc_full(dev, TcHandle::ROOT, None, config).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_qdisc_full"))]
pub async fn add_qdisc_full(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
handle: Option<TcHandle>,
config: impl QdiscConfig,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.add_qdisc_by_index_full(ifindex, parent, handle, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_qdisc_by_index"))]
pub async fn add_qdisc_by_index(&self, ifindex: u32, config: impl QdiscConfig) -> Result<()> {
self.add_qdisc_by_index_full(ifindex, TcHandle::ROOT, None, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_qdisc_by_index_full"))]
pub async fn add_qdisc_by_index_full(
&self,
ifindex: u32,
parent: TcHandle,
handle: Option<TcHandle>,
config: impl QdiscConfig,
) -> Result<()> {
let parent_handle = parent.as_raw();
let qdisc_handle = handle.map(|h| h.as_raw()).unwrap_or(0);
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(qdisc_handle);
let mut builder = create_request(NlMsgType::RTM_NEWQDISC);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, config.kind());
let options_token = builder.nest_start(TcaAttr::Options as u16);
config.write_options(&mut builder)?;
builder.nest_end(options_token);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("add_qdisc"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_qdisc"))]
pub async fn del_qdisc(&self, dev: impl Into<InterfaceRef>, parent: TcHandle) -> Result<()> {
self.del_qdisc_full(dev, parent, None).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_qdisc_full"))]
pub async fn del_qdisc_full(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
handle: Option<TcHandle>,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.del_qdisc_by_index_full(ifindex, parent, handle).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_qdisc_by_index"))]
pub async fn del_qdisc_by_index(&self, ifindex: u32, parent: TcHandle) -> Result<()> {
self.del_qdisc_by_index_full(ifindex, parent, None).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_qdisc_by_index_full"))]
pub async fn del_qdisc_by_index_full(
&self,
ifindex: u32,
parent: TcHandle,
handle: Option<TcHandle>,
) -> Result<()> {
let parent_handle = parent.as_raw();
let qdisc_handle = handle.map(|h| h.as_raw()).unwrap_or(0);
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(qdisc_handle);
let mut builder = ack_request(NlMsgType::RTM_DELQDISC);
builder.append(&tcmsg);
self.send_ack(builder).await.map_err(|e| {
if e.is_not_found() {
Error::QdiscNotFound {
kind: handle.unwrap_or(parent).to_string(),
interface: format!("ifindex {ifindex}"),
}
} else {
e.with_context(format!("del_qdisc(ifindex {ifindex}, parent={})", parent))
}
})
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "replace_qdisc"))]
pub async fn replace_qdisc(
&self,
dev: impl Into<InterfaceRef>,
config: impl QdiscConfig,
) -> Result<()> {
self.replace_qdisc_full(dev, TcHandle::ROOT, None, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "replace_qdisc_full"))]
pub async fn replace_qdisc_full(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
handle: Option<TcHandle>,
config: impl QdiscConfig,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.replace_qdisc_by_index_full(ifindex, parent, handle, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "replace_qdisc_by_index"))]
pub async fn replace_qdisc_by_index(
&self,
ifindex: u32,
config: impl QdiscConfig,
) -> Result<()> {
self.replace_qdisc_by_index_full(ifindex, TcHandle::ROOT, None, config)
.await
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "replace_qdisc_by_index_full")
)]
pub async fn replace_qdisc_by_index_full(
&self,
ifindex: u32,
parent: TcHandle,
handle: Option<TcHandle>,
config: impl QdiscConfig,
) -> Result<()> {
let parent_handle = parent.as_raw();
let qdisc_handle = handle.map(|h| h.as_raw()).unwrap_or(0);
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(qdisc_handle);
let mut builder = replace_request(NlMsgType::RTM_NEWQDISC);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, config.kind());
let options_token = builder.nest_start(TcaAttr::Options as u16);
config.write_options(&mut builder)?;
builder.nest_end(options_token);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("replace_qdisc"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "change_qdisc"))]
pub async fn change_qdisc(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
config: impl QdiscConfig,
) -> Result<()> {
self.change_qdisc_full(dev, parent, None, config).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "change_qdisc_full"))]
pub async fn change_qdisc_full(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
handle: Option<TcHandle>,
config: impl QdiscConfig,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.change_qdisc_by_index_full(ifindex, parent, handle, config)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "change_qdisc_by_index"))]
pub async fn change_qdisc_by_index(
&self,
ifindex: u32,
parent: TcHandle,
config: impl QdiscConfig,
) -> Result<()> {
self.change_qdisc_by_index_full(ifindex, parent, None, config)
.await
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(method = "change_qdisc_by_index_full")
)]
pub async fn change_qdisc_by_index_full(
&self,
ifindex: u32,
parent: TcHandle,
handle: Option<TcHandle>,
config: impl QdiscConfig,
) -> Result<()> {
let parent_handle = parent.as_raw();
let qdisc_handle = handle.map(|h| h.as_raw()).unwrap_or(0);
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(qdisc_handle);
let kind = config.kind().to_string();
let mut builder = ack_request(NlMsgType::RTM_NEWQDISC);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, &kind);
let options_token = builder.nest_start(TcaAttr::Options as u16);
config.write_options(&mut builder)?;
builder.nest_end(options_token);
self.send_ack(builder).await.map_err(|e| {
if e.is_not_found() {
Error::QdiscNotFound {
kind,
interface: format!("ifindex {ifindex}"),
}
} else {
e.with_context(format!(
"change_qdisc(ifindex {ifindex}, parent={})",
parent
))
}
})
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "apply_netem"))]
pub async fn apply_netem(
&self,
dev: impl Into<InterfaceRef>,
config: NetemConfig,
) -> Result<()> {
let dev = dev.into();
match self.replace_qdisc(dev.clone(), config.clone()).await {
Ok(()) => Ok(()),
Err(e) if e.is_not_found() => self.add_qdisc(dev, config).await,
Err(e) => Err(e),
}
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "apply_netem_by_index"))]
pub async fn apply_netem_by_index(&self, ifindex: u32, config: NetemConfig) -> Result<()> {
match self.replace_qdisc_by_index(ifindex, config.clone()).await {
Ok(()) => Ok(()),
Err(e) if e.is_not_found() => self.add_qdisc_by_index(ifindex, config).await,
Err(e) => Err(e),
}
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_netem"))]
pub async fn del_netem(&self, dev: impl Into<InterfaceRef>) -> Result<()> {
self.del_qdisc(dev, TcHandle::ROOT).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_netem_by_index"))]
pub async fn del_netem_by_index(&self, ifindex: u32) -> Result<()> {
self.del_qdisc_by_index(ifindex, TcHandle::ROOT).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_class"))]
pub async fn add_class(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
classid: TcHandle,
kind: &str,
params: &[&str],
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.add_class_by_index(ifindex, parent, classid, kind, params)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "add_class_by_index"))]
pub async fn add_class_by_index(
&self,
ifindex: u32,
parent: TcHandle,
classid: TcHandle,
kind: &str,
params: &[&str],
) -> Result<()> {
let parent_handle = parent.as_raw();
let class_handle = classid.as_raw();
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(class_handle);
let mut builder = create_request(NlMsgType::RTM_NEWTCLASS);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, kind);
let params: Vec<String> = params.iter().map(|s| s.to_string()).collect();
add_class_options(&mut builder, kind, ¶ms)?;
self.send_ack(builder)
.await
.map_err(|e| e.with_context("add_class"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_class"))]
pub async fn del_class(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
classid: TcHandle,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.del_class_by_index(ifindex, parent, classid).await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "del_class_by_index"))]
pub async fn del_class_by_index(
&self,
ifindex: u32,
parent: TcHandle,
classid: TcHandle,
) -> Result<()> {
let parent_handle = parent.as_raw();
let class_handle = classid.as_raw();
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(class_handle);
let mut builder = ack_request(NlMsgType::RTM_DELTCLASS);
builder.append(&tcmsg);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("del_class"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "change_class"))]
pub async fn change_class(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
classid: TcHandle,
kind: &str,
params: &[&str],
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.change_class_by_index(ifindex, parent, classid, kind, params)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "change_class_by_index"))]
pub async fn change_class_by_index(
&self,
ifindex: u32,
parent: TcHandle,
classid: TcHandle,
kind: &str,
params: &[&str],
) -> Result<()> {
let parent_handle = parent.as_raw();
let class_handle = classid.as_raw();
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(class_handle);
let mut builder = ack_request(NlMsgType::RTM_NEWTCLASS);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, kind);
let params: Vec<String> = params.iter().map(|s| s.to_string()).collect();
add_class_options(&mut builder, kind, ¶ms)?;
self.send_ack(builder)
.await
.map_err(|e| e.with_context("change_class"))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "replace_class"))]
pub async fn replace_class(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
classid: TcHandle,
kind: &str,
params: &[&str],
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.replace_class_by_index(ifindex, parent, classid, kind, params)
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "replace_class_by_index"))]
pub async fn replace_class_by_index(
&self,
ifindex: u32,
parent: TcHandle,
classid: TcHandle,
kind: &str,
params: &[&str],
) -> Result<()> {
let parent_handle = parent.as_raw();
let class_handle = classid.as_raw();
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(class_handle);
let mut builder = replace_request(NlMsgType::RTM_NEWTCLASS);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, kind);
let params: Vec<String> = params.iter().map(|s| s.to_string()).collect();
add_class_options(&mut builder, kind, ¶ms)?;
self.send_ack(builder)
.await
.map_err(|e| e.with_context("replace_class"))
}
pub async fn add_class_config<C: ClassConfig>(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
classid: TcHandle,
config: C,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.add_class_config_by_index(ifindex, parent, classid, config)
.await
}
pub async fn add_class_config_by_index<C: ClassConfig>(
&self,
ifindex: u32,
parent: TcHandle,
classid: TcHandle,
config: C,
) -> Result<()> {
let parent_handle = parent.as_raw();
let class_handle = classid.as_raw();
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(class_handle);
let mut builder = create_request(NlMsgType::RTM_NEWTCLASS);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, config.kind());
let options_token = builder.nest_start(TcaAttr::Options as u16);
config.write_options(&mut builder)?;
builder.nest_end(options_token);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("add_class"))
}
pub async fn change_class_config<C: ClassConfig>(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
classid: TcHandle,
config: C,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.change_class_config_by_index(ifindex, parent, classid, config)
.await
}
pub async fn change_class_config_by_index<C: ClassConfig>(
&self,
ifindex: u32,
parent: TcHandle,
classid: TcHandle,
config: C,
) -> Result<()> {
let parent_handle = parent.as_raw();
let class_handle = classid.as_raw();
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(class_handle);
let mut builder = ack_request(NlMsgType::RTM_NEWTCLASS);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, config.kind());
let options_token = builder.nest_start(TcaAttr::Options as u16);
config.write_options(&mut builder)?;
builder.nest_end(options_token);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("change_class"))
}
pub async fn replace_class_config<C: ClassConfig>(
&self,
dev: impl Into<InterfaceRef>,
parent: TcHandle,
classid: TcHandle,
config: C,
) -> Result<()> {
let ifindex = self.resolve_interface(&dev.into()).await?;
self.replace_class_config_by_index(ifindex, parent, classid, config)
.await
}
pub async fn replace_class_config_by_index<C: ClassConfig>(
&self,
ifindex: u32,
parent: TcHandle,
classid: TcHandle,
config: C,
) -> Result<()> {
let parent_handle = parent.as_raw();
let class_handle = classid.as_raw();
let tcmsg = TcMsg::new()
.with_ifindex(ifindex as i32)
.with_parent(parent_handle)
.with_handle(class_handle);
let mut builder = replace_request(NlMsgType::RTM_NEWTCLASS);
builder.append(&tcmsg);
builder.append_attr_str(TcaAttr::Kind as u16, config.kind());
let options_token = builder.nest_start(TcaAttr::Options as u16);
config.write_options(&mut builder)?;
builder.nest_end(options_token);
self.send_ack(builder)
.await
.map_err(|e| e.with_context("replace_class"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_netem_builder() {
use crate::util::Percent;
let config = NetemConfig::new()
.delay(Duration::from_millis(100))
.jitter(Duration::from_millis(10))
.delay_correlation(Percent::new(25.0))
.loss(Percent::new(1.0))
.build();
assert_eq!(config.delay, Some(Duration::from_millis(100)));
assert_eq!(config.jitter, Some(Duration::from_millis(10)));
assert_eq!(config.delay_correlation.as_percent(), 25.0);
assert_eq!(config.loss.as_percent(), 1.0);
assert_eq!(config.kind(), "netem");
}
#[test]
fn test_fq_codel_builder() {
let config = FqCodelConfig::new()
.target(Duration::from_millis(5))
.interval(Duration::from_millis(100))
.limit(10000)
.ecn(true)
.build();
assert_eq!(config.target, Some(Duration::from_millis(5)));
assert_eq!(config.interval, Some(Duration::from_millis(100)));
assert_eq!(config.limit, Some(10000));
assert!(config.ecn);
assert_eq!(config.kind(), "fq_codel");
}
#[test]
fn test_tbf_builder() {
use crate::util::{Bytes, Rate};
let config = TbfConfig::new()
.rate(Rate::bytes_per_sec(1_000_000))
.burst(Bytes::kib(32))
.limit(Bytes::kib(100))
.build();
assert_eq!(config.rate, Rate::bytes_per_sec(1_000_000));
assert_eq!(config.burst, Bytes::kib(32));
assert_eq!(config.limit, Bytes::kib(100));
assert_eq!(config.kind(), "tbf");
}
#[test]
fn test_netem_clamp() {
use crate::util::Percent;
let config = NetemConfig::new()
.loss(Percent::new(150.0)) .delay_correlation(Percent::new(-10.0)) .build();
assert_eq!(config.loss.as_percent(), 100.0);
assert_eq!(config.delay_correlation.as_percent(), 0.0);
}
#[test]
fn test_drr_builder() {
let config = DrrConfig::new().build();
assert_eq!(config.kind(), "drr");
}
#[test]
fn test_qfq_builder() {
let config = QfqConfig::new().build();
assert_eq!(config.kind(), "qfq");
}
#[test]
fn test_plug_builder() {
let config = PlugConfig::new().limit(10000).build();
assert_eq!(config.limit, Some(10000));
assert_eq!(config.kind(), "plug");
}
#[test]
fn test_mqprio_builder() {
let config = MqprioConfig::new()
.num_tc(4)
.map(&[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3])
.hw_offload(true)
.queues(&[(2, 0), (2, 2), (2, 4), (2, 6)])
.build();
assert_eq!(config.num_tc, 4);
assert!(config.hw);
assert_eq!(config.prio_tc_map[0], 0);
assert_eq!(config.prio_tc_map[3], 3);
assert_eq!(config.count[0], 2);
assert_eq!(config.offset[1], 2);
assert_eq!(config.kind(), "mqprio");
}
#[test]
fn test_etf_builder() {
let config = EtfConfig::new()
.clockid(1) .delta_ns(300000)
.deadline_mode(true)
.offload(true)
.skip_sock_check(false)
.build();
assert_eq!(config.clockid, 1);
assert_eq!(config.delta, 300000);
assert!(config.deadline_mode);
assert!(config.offload);
assert!(!config.skip_sock_check);
assert_eq!(config.kind(), "etf");
}
#[test]
fn test_hfsc_builder() {
let config = HfscConfig::new().default_class(0x10).build();
assert_eq!(config.default_class, 0x10);
assert_eq!(config.kind(), "hfsc");
}
#[test]
fn test_taprio_builder() {
let config = TaprioConfig::new()
.num_tc(2)
.map(&[0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
.queues(&[(1, 0), (1, 1)])
.clockid(11) .base_time(1000000000)
.cycle_time(1000000)
.entry(TaprioSchedEntry::set_gates(0x1, 500000))
.entry(TaprioSchedEntry::set_gates(0x2, 500000))
.full_offload(true)
.build();
assert_eq!(config.num_tc, 2);
assert_eq!(config.clockid, 11);
assert_eq!(config.base_time, 1000000000);
assert_eq!(config.cycle_time, 1000000);
assert_eq!(config.entries.len(), 2);
assert_eq!(config.entries[0].gate_mask, 0x1);
assert_eq!(config.entries[0].interval, 500000);
assert_eq!(config.entries[1].gate_mask, 0x2);
assert!(config.flags & 2 != 0); assert_eq!(config.kind(), "taprio");
}
#[test]
fn test_htb_class_config_typed() {
use crate::util::Rate;
let config = HtbClassConfig::new(Rate::mbit(100))
.ceil(Rate::mbit(500))
.prio(1)
.quantum(1500)
.build();
assert_eq!(config.rate, Rate::mbit(100));
assert_eq!(config.ceil, Some(Rate::mbit(500)));
assert_eq!(config.prio, Some(1));
assert_eq!(config.quantum, Some(1500));
assert_eq!(config.kind(), "htb");
}
#[test]
fn test_htb_class_config_from_string() {
use crate::util::Rate;
let config = HtbClassConfig::new("100mbit".parse::<Rate>().unwrap())
.ceil("500mbit".parse::<Rate>().unwrap())
.prio(2)
.build();
assert_eq!(config.rate, Rate::mbit(100));
assert_eq!(config.ceil, Some(Rate::mbit(500)));
assert_eq!(config.prio, Some(2));
assert_eq!(config.kind(), "htb");
}
#[test]
fn test_htb_class_config_burst() {
use crate::util::{Bytes, Rate};
let config = HtbClassConfig::new(Rate::bytes_per_sec(1_000_000))
.burst(Bytes::new(16384))
.cburst(Bytes::new(32768))
.mtu(9000)
.mpu(64)
.overhead(14)
.build();
assert_eq!(config.burst, Some(Bytes::new(16384)));
assert_eq!(config.cburst, Some(Bytes::new(32768)));
assert_eq!(config.mtu, 9000);
assert_eq!(config.mpu, 64);
assert_eq!(config.overhead, 14);
}
#[test]
fn test_htb_class_config_prio_clamp() {
use crate::util::Rate;
let config = HtbClassConfig::new(Rate::bytes_per_sec(1_000_000))
.prio(100) .build();
assert_eq!(config.prio, Some(7));
}
#[test]
fn test_htb_class_config_defaults() {
use crate::util::Rate;
let config = HtbClassConfig::new(Rate::bytes_per_sec(1_000_000)).build();
assert_eq!(config.ceil, None);
assert_eq!(config.burst, None);
assert_eq!(config.cburst, None);
assert_eq!(config.prio, None);
assert_eq!(config.quantum, None);
assert_eq!(config.mtu, 1600);
assert_eq!(config.mpu, 0);
assert_eq!(config.overhead, 0);
}
}