below_tc/
lib.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![deny(clippy::all)]
16
17mod errors;
18mod types;
19
20#[cfg(test)]
21mod test;
22
23use std::borrow::Borrow;
24use std::borrow::BorrowMut;
25use std::collections::BTreeMap;
26
27use errors::TcError;
28use netlink_packet_core::NetlinkHeader;
29use netlink_packet_core::NetlinkMessage;
30use netlink_packet_core::NetlinkPayload;
31use netlink_packet_core::NLM_F_DUMP;
32use netlink_packet_core::NLM_F_REQUEST;
33use netlink_packet_route::tc::TcMessage;
34use netlink_packet_route::RouteNetlinkMessage;
35use netlink_sys::constants::NETLINK_ROUTE;
36use netlink_sys::Socket;
37use netlink_sys::SocketAddr;
38use nix::net::if_;
39pub use types::FqCodelQDisc;
40pub use types::FqCodelQdStats;
41pub use types::FqCodelXStats;
42pub use types::QDisc;
43pub use types::TcStat;
44pub use types::XStats;
45
46pub type TcStats = Vec<TcStat>;
47type Result<T> = std::result::Result<T, TcError>;
48
49/// Get list of all `tc` qdiscs.
50pub fn tc_stats() -> Result<TcStats> {
51    let ifaces = get_interfaces()?;
52    read_tc_stats(ifaces, &get_netlink_qdiscs)
53}
54
55fn read_tc_stats(
56    interfaces: BTreeMap<u32, String>,
57    netlink_retriever: &dyn Fn() -> Result<Vec<TcMessage>>,
58) -> Result<TcStats> {
59    let messages = netlink_retriever()?;
60    let tc_stats = messages
61        .into_iter()
62        .filter_map(|msg| {
63            interfaces
64                .get(&(msg.header.index as u32))
65                .cloned()
66                .map(|if_name| TcStat::new(if_name, &msg))
67        })
68        .collect();
69
70    Ok(tc_stats)
71}
72
73/// Open a netlink socket to retrieve `tc` qdiscs.
74/// The underlying library sends a message of type `RTM_GETQDISC` to the kernel.
75/// The kernel responds with a message of type `RTM_NEWQDISC` for each qdisc.
76fn get_netlink_qdiscs() -> Result<Vec<TcMessage>> {
77    // open a socket
78    let socket = Socket::new(NETLINK_ROUTE).map_err(|e| TcError::Netlink(e.to_string()))?;
79    socket
80        .connect(&SocketAddr::new(0, 0))
81        .map_err(|e| TcError::Netlink(e.to_string()))?;
82
83    // create a netlink request
84    let mut nl_hdr = NetlinkHeader::default();
85    nl_hdr.flags = NLM_F_REQUEST | NLM_F_DUMP;
86    let msg = RouteNetlinkMessage::GetQueueDiscipline(TcMessage::default());
87    let mut packet = NetlinkMessage::new(nl_hdr, NetlinkPayload::from(msg));
88    packet.finalize();
89    let mut buf = vec![0; packet.header.length as usize];
90    packet.serialize(buf[..].borrow_mut());
91
92    // send the request
93    socket
94        .send(buf[..].borrow(), 0)
95        .map_err(|e| TcError::Netlink(e.to_string()))?;
96
97    // receive the response
98    let mut recv_buf = vec![0; 4096];
99    let mut offset = 0;
100    let mut response = Vec::new();
101    'out: while let Ok(size) = socket.recv(&mut recv_buf[..].borrow_mut(), 0) {
102        loop {
103            let bytes = recv_buf[offset..].borrow();
104            let rx_packet = <NetlinkMessage<RouteNetlinkMessage>>::deserialize(bytes)
105                .map_err(|e| TcError::Netlink(e.to_string()))?;
106            response.push(rx_packet.clone());
107            let payload = rx_packet.payload;
108            if let NetlinkPayload::Error(err) = payload {
109                return Err(TcError::Netlink(err.to_string()));
110            }
111            if let NetlinkPayload::Done(_) = payload {
112                break 'out;
113            }
114
115            offset += rx_packet.header.length as usize;
116            if offset == size || rx_packet.header.length == 0 {
117                offset = 0;
118                break;
119            }
120        }
121    }
122
123    let mut tc_msgs = Vec::new();
124    for msg in response {
125        if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewQueueDiscipline(tc)) =
126            msg.payload
127        {
128            tc_msgs.push(tc);
129        }
130    }
131
132    Ok(tc_msgs)
133}
134
135/// Get a map of interface index to interface name.
136fn get_interfaces() -> Result<BTreeMap<u32, String>> {
137    let ifaces = if_::if_nameindex().map_err(|e| TcError::ReadInterfaces(e.to_string()))?;
138    let if_map = ifaces
139        .iter()
140        .map(|iface| {
141            let index = iface.index();
142            let name = if let Ok(name) = iface.name().to_str() {
143                name.to_string()
144            } else {
145                String::new()
146            };
147            (index, name)
148        })
149        .collect::<BTreeMap<u32, String>>();
150
151    Ok(if_map)
152}