netlink_tc/
tc.rs

1use crate::{
2    class::htb::Htb,
3    constants::*,
4    errors::TcError,
5    link, netlink,
6    qdiscs::{
7        clsact::Clsact,
8        fq_codel::{FqCodel, FqCodelXStats},
9    },
10    types::*,
11    HtbXstats,
12};
13
14/// `qdiscs` returns a list of all qdiscs on the system.
15/// The underlying implementation makes a netlink call with the `RTM_GETQDISC` command.
16pub fn qdiscs<T: netlink::NetlinkConnection>() -> Result<Vec<Tc>, TcError> {
17    let mut tcs = Vec::new();
18
19    let messages = T::new()?.qdiscs()?;
20    for message in &messages {
21        let tc = TcMessage {
22            index: message.header.index as u32,
23            handle: message.header.handle,
24            parent: message.header.parent,
25        };
26        let mut attribute = Attribute::default();
27
28        let mut options = Vec::new();
29        let mut xstats = Vec::new();
30        for attr in &message.attrs {
31            match attr {
32                TcAttr::Kind(kind) => attribute.kind = kind.to_string(),
33                TcAttr::Options(opts) => options = opts.to_vec(),
34                TcAttr::Stats(bytes) => attribute.stats = parse_stats(bytes).ok(),
35                TcAttr::Xstats(bytes) => xstats.extend(bytes.as_slice()),
36                TcAttr::Stats2(stats) => attribute.stats2 = parse_stats2(stats).ok(),
37                _ => (),
38            }
39        }
40
41        attribute.qdisc = parse_qdiscs(attribute.kind.as_str(), options);
42        attribute.xstats = parse_xstats(attribute.kind.as_str(), xstats.as_slice()).ok();
43
44        tcs.push(Tc {
45            msg: tc,
46            attr: attribute,
47        });
48    }
49
50    Ok(tcs)
51}
52
53/// `class_for_index` returns a list of all classes for a given interface index.
54/// The underlying implementation makes a netlink call with the `RTM_GETCLASS` command.
55pub fn class_for_index<T: netlink::NetlinkConnection>(index: u32) -> Result<Vec<Tc>, TcError> {
56    let mut tcs = Vec::new();
57
58    let messages = T::new()?.classes(index as i32)?;
59    for message in &messages {
60        let tc = TcMessage {
61            index,
62            handle: message.header.handle,
63            parent: message.header.parent,
64        };
65        let mut attribute = Attribute::default();
66
67        let mut opts = vec![];
68        let mut xstats = Vec::new();
69        for attr in &message.attrs {
70            match attr {
71                TcAttr::Kind(kind) => attribute.kind = kind.to_string(),
72                TcAttr::Options(tc_opts) => opts = tc_opts.to_vec(),
73                TcAttr::Stats(bytes) => attribute.stats = parse_stats(bytes).ok(),
74                TcAttr::Xstats(bytes) => xstats.extend(bytes.as_slice()),
75                TcAttr::Stats2(stats) => attribute.stats2 = parse_stats2(stats).ok(),
76                _ => (),
77            }
78        }
79
80        attribute.class = parse_classes(attribute.kind.as_str(), opts);
81        attribute.xstats = parse_xstats(attribute.kind.as_str(), xstats.as_slice()).ok();
82
83        tcs.push(Tc {
84            msg: tc,
85            attr: attribute,
86        });
87    }
88
89    Ok(tcs)
90}
91
92/// `class` returns a list of all classes for a given interface name.
93/// It retrieves the list of links and then calls `class_for_index`
94/// for the link with the matching name.
95pub fn class<T: netlink::NetlinkConnection>(name: &str) -> Result<Vec<Tc>, TcError> {
96    let links = link::links::<T>()?;
97
98    if let Some(link) = links.iter().find(|link| link.name == name) {
99        class_for_index::<T>(link.index)
100    } else {
101        Ok(Vec::new())
102    }
103}
104
105/// `classes` returns a list of all classes on the system.
106/// It retrieves the list of links and then calls `classes` for each link.
107pub fn classes<T: netlink::NetlinkConnection>() -> Result<Vec<Tc>, TcError> {
108    let mut tcs = Vec::new();
109
110    let links = link::links::<T>()?;
111    for link in links {
112        tcs.append(&mut class_for_index::<T>(link.index)?);
113    }
114
115    Ok(tcs)
116}
117
118pub fn tc_stats<T: netlink::NetlinkConnection>() -> Result<Vec<Tc>, TcError> {
119    let mut tcs = qdiscs::<T>()?;
120    tcs.append(&mut classes::<T>()?);
121
122    Ok(tcs)
123}
124
125fn parse_stats(bytes: &[u8]) -> Result<Stats, TcError> {
126    bincode::deserialize(bytes).map_err(TcError::UnmarshalStruct)
127}
128
129fn parse_stats2(stats2: &Vec<TcStats2>) -> Result<Stats2, TcError> {
130    let mut stats = Stats2::default();
131    let mut errors = Vec::new();
132    for stat in stats2 {
133        match stat {
134            TcStats2::StatsBasic(bytes) => match bincode::deserialize(bytes.as_slice()) {
135                Ok(stats_basic) => stats.basic = Some(stats_basic),
136                Err(e) => errors.push(format!("Failed to parse StatsBasic: {e}")),
137            },
138            TcStats2::StatsQueue(bytes) => match bincode::deserialize(bytes.as_slice()) {
139                Ok(stats_queue) => stats.queue = Some(stats_queue),
140                Err(e) => errors.push(format!("Failed to parse StatsQueue: {e}")),
141            },
142            // TcStats2::StatsApp(bytes) => stats.app = bincode::deserialize(bytes.as_slice()).ok(),
143            _ => (),
144        }
145    }
146
147    if !errors.is_empty() {
148        let message = errors.join(", ");
149        Err(TcError::UnmarshalStructs(message))
150    } else {
151        Ok(stats)
152    }
153}
154
155fn parse_qdiscs(kind: &str, opts: Vec<TcOption>) -> Option<QDisc> {
156    match kind {
157        FQ_CODEL => Some(QDisc::FqCodel(FqCodel::new(opts))),
158        CLSACT => Some(QDisc::Clsact(Clsact {})),
159        HTB => Htb::new(opts).init.map(QDisc::Htb),
160        _ => None,
161    }
162}
163
164fn parse_classes(kind: &str, opts: Vec<TcOption>) -> Option<Class> {
165    match kind {
166        HTB => Some(Class::Htb(Htb::new(opts))),
167        _ => None,
168    }
169}
170
171fn parse_xstats(kind: &str, bytes: &[u8]) -> Result<XStats, TcError> {
172    match kind {
173        FQ_CODEL => FqCodelXStats::new(bytes).map(XStats::FqCodel),
174        HTB => HtbXstats::new(bytes).map(XStats::Htb),
175        _ => Err(TcError::UnimplementedAttribute(format!(
176            "XStats for {kind}"
177        ))),
178    }
179}