netlink_tc/
netlink.rs

1use std::vec;
2
3use netlink_packet_core::{
4    NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST,
5};
6use netlink_packet_route::{link, tc as netlink_tc, LinkMessage, RtnlMessage, TcMessage};
7use netlink_packet_utils::{nla::Nla, Emitable};
8use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr};
9
10use crate::{errors::NetlinkError, types::*};
11
12/// A trait for a netlink connection.
13///
14/// This trait allows for mocking the netlink connection in tests.
15pub trait NetlinkConnection {
16    /// Create a new netlink connection.
17    /// Initialize a new netlink socket and connect to the kernel.
18    fn new() -> Result<Self, NetlinkError>
19    where
20        Self: Sized;
21
22    /// Get all qdiscs from Netlink.
23    fn qdiscs(&self) -> Result<Vec<TcMsg>, NetlinkError>;
24
25    /// Get all classes from Netlink.
26    fn classes(&self, index: i32) -> Result<Vec<TcMsg>, NetlinkError>;
27
28    /// Get all links from Netlink.
29    fn links(&self) -> Result<Vec<LinkMsg>, NetlinkError>;
30}
31
32/// A struct for communicating with the kernel via netlink.
33pub struct Netlink {
34    socket: Socket,
35}
36
37impl NetlinkConnection for Netlink {
38    fn new() -> Result<Self, NetlinkError>
39    where
40        Self: Sized,
41    {
42        let socket =
43            Socket::new(NETLINK_ROUTE).map_err(|err| NetlinkError::Socket(Box::new(err)))?;
44        socket
45            .connect(&SocketAddr::new(0, 0))
46            .map_err(|err| NetlinkError::Socket(Box::new(err)))?;
47        Ok(Self { socket })
48    }
49
50    fn qdiscs(&self) -> Result<Vec<TcMsg>, NetlinkError> {
51        send_get_qdisc_request(&self.socket)?;
52
53        let mut receive_buffer = vec![0; 4096];
54        let mut offset = 0;
55
56        let mut tc_messages = Vec::new();
57        while let Ok(size) = self.socket.recv(&mut &mut receive_buffer[..], 0) {
58            loop {
59                let bytes = &receive_buffer[offset..];
60                let rx_packet = <NetlinkMessage<RtnlMessage>>::deserialize(bytes).unwrap();
61
62                let payload = rx_packet.payload;
63                match payload {
64                    NetlinkPayload::InnerMessage(RtnlMessage::NewQueueDiscipline(message)) => {
65                        tc_messages.push(message.clone())
66                    }
67                    NetlinkPayload::Error(error) => {
68                        return Err(NetlinkError::Netlink(error.to_string()))
69                    }
70                    NetlinkPayload::Done(_) => return Ok(to_tc(tc_messages)),
71                    _ => {}
72                }
73
74                offset += rx_packet.header.length as usize;
75                if offset == size || rx_packet.header.length == 0 {
76                    offset = 0;
77                    break;
78                }
79            }
80        }
81
82        Ok(to_tc(tc_messages))
83    }
84
85    fn classes(&self, index: i32) -> Result<Vec<TcMsg>, NetlinkError> {
86        send_get_class_request(&self.socket, index)?;
87
88        let mut receive_buffer = vec![0; 4096];
89        let mut offset = 0;
90
91        let mut tc_messages = Vec::new();
92        while let Ok(size) = self.socket.recv(&mut &mut receive_buffer[..], 0) {
93            loop {
94                let bytes = &receive_buffer[offset..];
95                let rx_packet = <NetlinkMessage<RtnlMessage>>::deserialize(bytes).unwrap();
96
97                let payload = rx_packet.payload;
98                match payload {
99                    NetlinkPayload::InnerMessage(RtnlMessage::NewTrafficClass(message)) => {
100                        tc_messages.push(message.clone())
101                    }
102                    NetlinkPayload::Error(error) => {
103                        return Err(NetlinkError::Netlink(error.to_string()))
104                    }
105                    NetlinkPayload::Done(_) => return Ok(to_tc(tc_messages)),
106                    _ => {}
107                }
108
109                offset += rx_packet.header.length as usize;
110                if offset == size || rx_packet.header.length == 0 {
111                    offset = 0;
112                    break;
113                }
114            }
115        }
116
117        Ok(to_tc(tc_messages))
118    }
119
120    fn links(&self) -> Result<Vec<LinkMsg>, NetlinkError> {
121        send_get_link_request(&self.socket)?;
122
123        let mut receive_buffer = vec![0; 4096];
124        let mut offset = 0;
125
126        let mut link_messages = Vec::new();
127        while let Ok(size) = self.socket.recv(&mut &mut receive_buffer[..], 0) {
128            loop {
129                let bytes = &receive_buffer[offset..];
130                let rx_packet = <NetlinkMessage<RtnlMessage>>::deserialize(bytes).unwrap();
131
132                let payload = rx_packet.payload;
133                match payload {
134                    NetlinkPayload::InnerMessage(RtnlMessage::NewLink(message)) => {
135                        link_messages.push(message.clone())
136                    }
137                    NetlinkPayload::Error(error) => {
138                        return Err(NetlinkError::Netlink(error.to_string()))
139                    }
140                    NetlinkPayload::Done(_) => return Ok(to_link(link_messages)),
141                    _ => {}
142                }
143
144                offset += rx_packet.header.length as usize;
145                if offset == size || rx_packet.header.length == 0 {
146                    offset = 0;
147                    break;
148                }
149            }
150        }
151
152        Ok(to_link(link_messages))
153    }
154}
155
156fn send_get_qdisc_request(socket: &Socket) -> Result<(), NetlinkError> {
157    let mut nl_hdr = NetlinkHeader::default();
158    nl_hdr.flags = NLM_F_REQUEST | NLM_F_DUMP;
159
160    let mut packet = NetlinkMessage::new(
161        nl_hdr,
162        NetlinkPayload::from(RtnlMessage::GetQueueDiscipline(TcMessage::default())),
163    );
164    packet.finalize();
165
166    let mut buf = vec![0; packet.header.length as usize];
167    packet.serialize(&mut buf[..]);
168
169    match socket.send(&buf[..], 0) {
170        Ok(_) => Ok(()),
171        Err(e) => Err(NetlinkError::Send(e.to_string())),
172    }
173}
174
175fn send_get_class_request(socket: &Socket, index: i32) -> Result<(), NetlinkError> {
176    let mut nl_hdr = NetlinkHeader::default();
177    nl_hdr.flags = NLM_F_REQUEST | NLM_F_DUMP;
178
179    let tc_hdr = netlink_tc::TcHeader {
180        index,
181        ..Default::default()
182    };
183    let mut tc_msg = TcMessage::default();
184    tc_msg.header = tc_hdr;
185    let mut packet = NetlinkMessage::new(
186        nl_hdr,
187        NetlinkPayload::from(RtnlMessage::GetTrafficClass(tc_msg)),
188    );
189    packet.finalize();
190
191    let mut buf = vec![0; packet.header.length as usize];
192    packet.serialize(&mut buf[..]);
193
194    match socket.send(&buf[..], 0) {
195        Ok(_) => Ok(()),
196        Err(e) => Err(NetlinkError::Send(e.to_string())),
197    }
198}
199
200fn send_get_link_request(socket: &Socket) -> Result<(), NetlinkError> {
201    let mut nl_hdr = NetlinkHeader::default();
202    nl_hdr.flags = NLM_F_REQUEST | NLM_F_DUMP;
203
204    let mut packet = NetlinkMessage::new(
205        nl_hdr,
206        NetlinkPayload::from(RtnlMessage::GetLink(LinkMessage::default())),
207    );
208    packet.finalize();
209
210    let mut buf = vec![0; packet.header.length as usize];
211    packet.serialize(&mut buf[..]);
212
213    match socket.send(&buf[..], 0) {
214        Ok(_) => Ok(()),
215        Err(e) => Err(NetlinkError::Send(e.to_string())),
216    }
217}
218
219fn to_tc(tc_messages: Vec<TcMessage>) -> Vec<TcMsg> {
220    tc_messages
221        .into_iter()
222        .map(|tc_message| {
223            let TcMessage {
224                header: tc_header,
225                nlas,
226                ..
227            } = tc_message;
228            let header = TcHeader {
229                index: tc_header.index,
230                handle: tc_header.handle,
231                parent: tc_header.parent,
232            };
233            let mut attrs = Vec::new();
234
235            for nla in nlas {
236                match nla {
237                    netlink_tc::Nla::Kind(kind) => attrs.push(TcAttr::Kind(kind)),
238                    netlink_tc::Nla::Options(tc_opts) => {
239                        let mut opts = Vec::new();
240                        for opt in tc_opts {
241                            match opt {
242                                netlink_tc::TcOpt::Ingress => {
243                                    let option = TcOption {
244                                        kind: 0u16, // TODO: what is Ingress kind?
245                                        bytes: vec![],
246                                    };
247                                    opts.push(option);
248                                }
249                                netlink_tc::TcOpt::U32(nla) => {
250                                    let mut buf = vec![0u8; nla.value_len()];
251                                    nla.emit_value(buf.as_mut_slice());
252                                    let option = TcOption {
253                                        kind: nla.kind(),
254                                        bytes: buf,
255                                    };
256                                    opts.push(option);
257                                }
258                                netlink_tc::TcOpt::Matchall(nla) => {
259                                    let mut buf = vec![0u8; nla.value_len()];
260                                    nla.emit_value(buf.as_mut_slice());
261                                    let option = TcOption {
262                                        kind: nla.kind(),
263                                        bytes: buf,
264                                    };
265                                    opts.push(option);
266                                }
267                                netlink_tc::TcOpt::Other(nla) => {
268                                    let mut buf = vec![0u8; nla.value_len()];
269                                    nla.emit_value(buf.as_mut_slice());
270                                    let option = TcOption {
271                                        kind: nla.kind(),
272                                        bytes: buf,
273                                    };
274                                    opts.push(option);
275                                }
276                                _ => (),
277                            };
278                        }
279                        attrs.push(TcAttr::Options(opts));
280                    }
281                    netlink_tc::Nla::Stats(tc_stats) => {
282                        let mut buf = vec![0u8; tc_stats.buffer_len()];
283                        tc_stats.emit(buf.as_mut_slice());
284                        attrs.push(TcAttr::Stats(buf));
285                    }
286                    netlink_tc::Nla::Stats2(tc_stats) => {
287                        let mut stats2 = Vec::new();
288                        for stat in tc_stats {
289                            match stat {
290                                netlink_tc::Stats2::StatsBasic(bytes) => {
291                                    stats2.push(TcStats2::StatsBasic(bytes))
292                                }
293                                netlink_tc::Stats2::StatsQueue(bytes) => {
294                                    stats2.push(TcStats2::StatsQueue(bytes))
295                                }
296                                netlink_tc::Stats2::StatsApp(bytes) => {
297                                    stats2.push(TcStats2::StatsApp(bytes))
298                                }
299                                _ => (),
300                            }
301                        }
302                        attrs.push(TcAttr::Stats2(stats2));
303                    }
304                    netlink_tc::Nla::XStats(bytes) => attrs.push(TcAttr::Xstats(bytes)),
305                    netlink_tc::Nla::Rate(bytes) => attrs.push(TcAttr::Rate(bytes)),
306                    netlink_tc::Nla::Fcnt(bytes) => attrs.push(TcAttr::Fcnt(bytes)),
307                    netlink_tc::Nla::Stab(bytes) => attrs.push(TcAttr::Stab(bytes)),
308                    netlink_tc::Nla::Chain(bytes) => attrs.push(TcAttr::Chain(bytes)),
309                    netlink_tc::Nla::HwOffload(byte) => attrs.push(TcAttr::HwOffload(byte)),
310                    _ => (),
311                }
312            }
313
314            TcMsg { header, attrs }
315        })
316        .collect()
317}
318
319fn to_link(link_messages: Vec<LinkMessage>) -> Vec<LinkMsg> {
320    link_messages
321        .into_iter()
322        .map(|link_message| {
323            let LinkMessage {
324                header: link_header,
325                nlas,
326                ..
327            } = link_message;
328            let header = LinkHeader {
329                index: link_header.index,
330            };
331
332            let mut name = String::new();
333            for nla in nlas {
334                if let link::nlas::Nla::IfName(if_name) = nla {
335                    name = if_name;
336                }
337            }
338
339            LinkMsg {
340                header,
341                attr: LinkAttr { name },
342            }
343        })
344        .collect()
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350
351    use crate::test_data;
352
353    #[test]
354    fn test_qdiscs_to_tc() {
355        let qdiscs = test_data::nl_qdiscs();
356        let tcs = to_tc(qdiscs);
357
358        // noqueue
359        // TcMessage { header: TcHeader { family: 0, index: 1, handle: 0, parent: 4294967295, info: 2 }, nlas: [Kind("noqueue"), HwOffload(0), Stats2([StatsBasic([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), StatsQueue([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])]), Stats(Stats { bytes: 0, packets: 0, drops: 0, overlimits: 0, bps: 0, pps: 0, qlen: 0, backlog: 0 })] }
360        fn test_no_queue(tc: &TcMsg) {
361            // header
362            assert_eq!(tc.header.index, 1);
363            assert_eq!(tc.header.handle, 0);
364            assert_eq!(tc.header.parent, 4294967295);
365
366            // attr
367            let attrs = &tc.attrs;
368            for attr in attrs {
369                match attr {
370                    TcAttr::Kind(kind) => assert_eq!(kind, "noqueue"),
371                    TcAttr::Stats(bytes) => assert_eq!(bytes, &vec![0u8; 36]),
372                    TcAttr::Stats2(stats) => {
373                        for stat in stats {
374                            match stat {
375                                TcStats2::StatsBasic(bytes) => assert_eq!(bytes, &vec![0u8; 16]),
376                                TcStats2::StatsQueue(bytes) => assert_eq!(bytes, &vec![0u8; 20]),
377                                _ => (),
378                            }
379                        }
380                    }
381                    TcAttr::HwOffload(byte) => assert_eq!(byte, &0),
382                    _ => (),
383                }
384            }
385        }
386
387        // mq
388        // TcMessage { header: TcHeader { family: 0, index: 2, handle: 0, parent: 4294967295, info: 1 }, nlas: [Kind("mq"), HwOffload(0), Stats2([StatsBasic([28, 146, 82, 7, 0, 0, 0, 0, 119, 55, 6, 0, 0, 0, 0, 0]), StatsQueue([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0])]), Stats(Stats { bytes: 122851868, packets: 407415, drops: 0, overlimits: 0, bps: 0, pps: 0, qlen: 0, backlog: 0 })] }
389        fn test_mq(tc: &TcMsg) {
390            // header
391            assert_eq!(tc.header.index, 2);
392            assert_eq!(tc.header.handle, 0);
393            assert_eq!(tc.header.parent, 4294967295);
394
395            // attr
396            let attrs = &tc.attrs;
397            for attr in attrs {
398                match attr {
399                    TcAttr::Kind(kind) => assert_eq!(kind, "mq"),
400                    TcAttr::Stats(bytes) => {
401                        assert_eq!(
402                            bytes,
403                            &vec![
404                                28, 146, 82, 7, 0, 0, 0, 0, 119, 55, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0,
405                                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
406                            ]
407                        );
408                    }
409                    TcAttr::Stats2(stats) => {
410                        for stat in stats {
411                            match stat {
412                                TcStats2::StatsBasic(bytes) => assert_eq!(
413                                    bytes,
414                                    &vec![28, 146, 82, 7, 0, 0, 0, 0, 119, 55, 6, 0, 0, 0, 0, 0,]
415                                ),
416                                TcStats2::StatsQueue(bytes) => assert_eq!(
417                                    bytes,
418                                    &vec![
419                                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0,
420                                        0,
421                                    ]
422                                ),
423                                _ => (),
424                            }
425                        }
426                    }
427                    TcAttr::HwOffload(byte) => assert_eq!(byte, &0),
428                    _ => (),
429                }
430            }
431        }
432
433        // fq_codel
434        // TcMessage { header: TcHeader { family: 0, index: 2, handle: 0, parent: 2, info: 1 }, nlas: [Kind("fq_codel"), Options([Other(DefaultNla { kind: 1, value: [135, 19, 0, 0] }), Other(DefaultNla { kind: 2, value: [0, 40, 0, 0] }), Other(DefaultNla { kind: 3, value: [159, 134, 1, 0] }), Other(DefaultNla { kind: 4, value: [1, 0, 0, 0] }), Other(DefaultNla { kind: 6, value: [234, 5, 0, 0] }), Other(DefaultNla { kind: 8, value: [64, 0, 0, 0] }), Other(DefaultNla { kind: 9, value: [0, 0, 0, 2] }), Other(DefaultNla { kind: 5, value: [0, 4, 0, 0] })]), HwOffload(0), Stats2([StatsApp([0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), StatsBasic([76, 222, 96, 2, 0, 0, 0, 0, 55, 135, 2, 0, 0, 0, 0, 0]), StatsQueue([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0])]), Stats(Stats { bytes: 39902796, packets: 165687, drops: 0, overlimits: 0, bps: 0, pps: 0, qlen: 0, backlog: 0 }), XStats([0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])] }
435        fn test_fq_codel(tc: &TcMsg) {
436            // header
437            assert_eq!(tc.header.index, 2);
438            assert_eq!(tc.header.handle, 0);
439            assert_eq!(tc.header.parent, 2);
440
441            // attr
442            let attrs = &tc.attrs;
443            for attr in attrs {
444                match attr {
445                    TcAttr::Kind(kind) => assert_eq!(kind, "fq_codel"),
446                    TcAttr::Options(opts) => {
447                        for opt in opts {
448                            match opt {
449                                TcOption { kind, bytes } => match kind {
450                                    1 => assert_eq!(bytes, &vec![135, 19, 0, 0]),
451                                    2 => assert_eq!(bytes, &vec![0, 40, 0, 0]),
452                                    3 => assert_eq!(bytes, &vec![159, 134, 1, 0]),
453                                    4 => assert_eq!(bytes, &vec![1, 0, 0, 0]),
454                                    5 => assert_eq!(bytes, &vec![0, 4, 0, 0]),
455                                    6 => assert_eq!(bytes, &vec![234, 5, 0, 0]),
456                                    8 => assert_eq!(bytes, &vec![64, 0, 0, 0]),
457                                    9 => assert_eq!(bytes, &vec![0, 0, 0, 2]),
458                                    _ => (),
459                                },
460                            }
461                        }
462                    }
463                    TcAttr::Stats(bytes) => assert_eq!(
464                        bytes,
465                        &vec![
466                            76, 222, 96, 2, 0, 0, 0, 0, 55, 135, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
467                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
468                        ]
469                    ),
470                    TcAttr::Stats2(stats) => {
471                        for stat in stats {
472                            match stat {
473                                TcStats2::StatsBasic(bytes) => assert_eq!(
474                                    bytes,
475                                    &vec![76, 222, 96, 2, 0, 0, 0, 0, 55, 135, 2, 0, 0, 0, 0, 0,]
476                                ),
477                                TcStats2::StatsQueue(bytes) => assert_eq!(
478                                    bytes,
479                                    &vec![
480                                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0,
481                                    ]
482                                ),
483                                TcStats2::StatsApp(bytes) => assert_eq!(
484                                    bytes,
485                                    &vec![
486                                        0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 0, 0,
487                                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
488                                        0,
489                                    ]
490                                ),
491                            }
492                        }
493                    }
494                    TcAttr::Xstats(bytes) => assert_eq!(
495                        bytes,
496                        &vec![
497                            0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 0, 0, 0, 0, 0, 0,
498                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
499                        ]
500                    ),
501                    TcAttr::HwOffload(byte) => assert_eq!(byte, &0),
502                    _ => (),
503                }
504            }
505        }
506
507        // htb
508        // TcMessage { header: TcHeader { family: 0, index: 3, handle: 65536, parent: 4294967295, info: 2 }, nlas: [Kind("htb"), Options([Other(DefaultNla { kind: 2, value: [17, 0, 3, 0, 10, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }), Other(DefaultNla { kind: 5, value: [232, 3, 0, 0] })]), HwOffload(0), Stats2([StatsBasic([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), StatsQueue([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])]), Stats(Stats { bytes: 0, packets: 0, drops: 0, overlimits: 0, bps: 0, pps: 0, qlen: 0, backlog: 0 })] }
509        fn test_htb(tc: &TcMsg) {
510            // header
511            assert_eq!(tc.header.index, 3);
512            assert_eq!(tc.header.handle, 65536);
513            assert_eq!(tc.header.parent, 4294967295);
514
515            // attr
516            let attrs = &tc.attrs;
517            for attr in attrs {
518                match attr {
519                    TcAttr::Kind(kind) => assert_eq!(kind, "htb"),
520                    TcAttr::Options(opts) => {
521                        for opt in opts {
522                            match opt {
523                                TcOption { kind, bytes } => match kind {
524                                    2 => assert_eq!(
525                                        bytes,
526                                        &vec![
527                                            17, 0, 3, 0, 10, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0,
528                                            0, 0, 0,
529                                        ]
530                                    ),
531                                    5 => assert_eq!(bytes, &vec![232, 3, 0, 0]),
532                                    _ => (),
533                                },
534                            }
535                        }
536                    }
537                    TcAttr::Stats(bytes) => assert_eq!(bytes, &vec![0u8; 36]),
538                    TcAttr::Stats2(stats2) => {
539                        for stat in stats2 {
540                            match stat {
541                                TcStats2::StatsBasic(bytes) => assert_eq!(bytes, &vec![0u8; 16]),
542                                TcStats2::StatsQueue(bytes) => assert_eq!(bytes, &vec![0u8; 20]),
543                                _ => (),
544                            }
545                        }
546                    }
547                    TcAttr::HwOffload(byte) => assert_eq!(byte, &0),
548                    _ => (),
549                }
550            }
551        }
552
553        test_no_queue(tcs.get(0).unwrap());
554        test_mq(tcs.get(1).unwrap());
555        test_fq_codel(tcs.get(2).unwrap());
556        test_htb(tcs.get(3).unwrap());
557    }
558
559    #[test]
560    fn test_classes_to_tc() {
561        let classes = test_data::nl_classes();
562        let tcs = to_tc(classes);
563
564        // htb
565        // TcMessage { header: TcHeader { family: 0, index: 3, handle: 65537, parent: 4294967295, info: 0 }, nlas: [Kind("htb"), Options([Other(DefaultNla { kind: 1, value: [0, 1, 0, 0, 0, 0, 0, 0, 72, 232, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 72, 232, 1, 0, 64, 13, 3, 0, 64, 13, 3, 0, 212, 48, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0] })]), Stats2([StatsBasic([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), StatsQueue([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), StatsApp([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 13, 3, 0, 64, 13, 3, 0])]), Stats(Stats { bytes: 0, packets: 0, drops: 0, overlimits: 0, bps: 0, pps: 0, qlen: 0, backlog: 0 }), XStats([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 13, 3, 0, 64, 13, 3, 0])] }
566        fn test_htb(tc: &TcMsg) {
567            // header
568            assert_eq!(tc.header.index, 3);
569            assert_eq!(tc.header.handle, 65537);
570            assert_eq!(tc.header.parent, 4294967295);
571
572            // attr
573            let attrs = &tc.attrs;
574            for attr in attrs {
575                match attr {
576                    TcAttr::Kind(kind) => assert_eq!(kind, "htb"),
577                    TcAttr::Options(opts) => {
578                        for opt in opts {
579                            match opt {
580                                TcOption { kind, bytes } => match kind {
581                                    1 => assert_eq!(
582                                        bytes,
583                                        &vec![
584                                            0, 1, 0, 0, 0, 0, 0, 0, 72, 232, 1, 0, 0, 1, 0, 0, 0,
585                                            0, 0, 0, 72, 232, 1, 0, 64, 13, 3, 0, 64, 13, 3, 0,
586                                            212, 48, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0,
587                                        ]
588                                    ),
589                                    _ => (),
590                                },
591                            }
592                        }
593                    }
594                    TcAttr::Stats(bytes) => assert_eq!(bytes, &vec![0u8; 36]),
595                    TcAttr::Stats2(stats2) => {
596                        for stat in stats2 {
597                            match stat {
598                                TcStats2::StatsBasic(bytes) => assert_eq!(bytes, &vec![0u8; 16]),
599                                TcStats2::StatsQueue(bytes) => assert_eq!(bytes, &vec![0u8; 20]),
600                                TcStats2::StatsApp(bytes) => assert_eq!(
601                                    bytes,
602                                    &vec![
603                                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 13, 3, 0, 64, 13,
604                                        3, 0,
605                                    ]
606                                ),
607                            }
608                        }
609                    }
610                    TcAttr::Xstats(bytes) => assert_eq!(
611                        bytes,
612                        &vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 13, 3, 0, 64, 13, 3, 0,]
613                    ),
614                    _ => (),
615                }
616            }
617        }
618
619        test_htb(tcs.get(0).unwrap());
620    }
621
622    #[test]
623    fn test_to_link() {
624        let links = test_data::nl_links();
625        let link_msgs = to_link(links);
626
627        let link = link_msgs.get(0).unwrap();
628        assert_eq!(link.header.index, 1);
629        assert_eq!(link.attr.name, "eth0");
630    }
631}