rtnetlink/traffic_control/
add_qdisc.rs

1// SPDX-License-Identifier: MIT
2
3use futures::stream::StreamExt;
4use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST};
5use netlink_packet_route::{
6    tc::constants::{TC_H_INGRESS, TC_H_MAJ_MASK, TC_H_MIN_MASK, TC_H_ROOT},
7    tc::nlas::Nla,
8    RtnlMessage, TcMessage, TC_H_MAKE,
9};
10
11use crate::{try_nl, Error, Handle};
12
13pub struct QDiscNewRequest {
14    handle: Handle,
15    message: TcMessage,
16    flags: u16,
17}
18
19impl QDiscNewRequest {
20    pub(crate) fn new(handle: Handle, message: TcMessage, flags: u16) -> Self {
21        Self {
22            handle,
23            message,
24            flags: NLM_F_REQUEST | flags,
25        }
26    }
27
28    /// Execute the request
29    pub async fn execute(self) -> Result<(), Error> {
30        let Self {
31            mut handle,
32            message,
33            flags,
34        } = self;
35
36        let mut req =
37            NetlinkMessage::from(RtnlMessage::NewQueueDiscipline(message));
38        req.header.flags = NLM_F_ACK | flags;
39
40        let mut response = handle.request(req)?;
41        while let Some(message) = response.next().await {
42            try_nl!(message);
43        }
44        Ok(())
45    }
46
47    /// Set handle,
48    pub fn handle(mut self, maj: u16, min: u16) -> Self {
49        self.message.header.handle = TC_H_MAKE!((maj as u32) << 16, min as u32);
50        self
51    }
52
53    /// Set parent to root.
54    pub fn root(mut self) -> Self {
55        self.message.header.parent = TC_H_ROOT;
56        self
57    }
58
59    /// Set parent
60    pub fn parent(mut self, parent: u32) -> Self {
61        self.message.header.parent = parent;
62        self
63    }
64
65    /// New a ingress qdisc
66    pub fn ingress(mut self) -> Self {
67        self.message.header.parent = TC_H_INGRESS;
68        self.message.header.handle = 0xffff0000;
69        self.message.nlas.push(Nla::Kind("ingress".to_string()));
70        self
71    }
72}
73
74#[cfg(test)]
75mod test {
76    use std::{fs::File, os::unix::io::AsRawFd, path::Path};
77
78    use futures::stream::TryStreamExt;
79    use nix::sched::{setns, CloneFlags};
80    use tokio::runtime::Runtime;
81
82    use super::*;
83    use crate::{new_connection, NetworkNamespace, NETNS_PATH, SELF_NS_PATH};
84    use netlink_packet_route::{
85        tc::nlas::Nla::{HwOffload, Kind},
86        LinkMessage, AF_UNSPEC,
87    };
88
89    const TEST_NS: &str = "netlink_test_qdisc_ns";
90    const TEST_DUMMY: &str = "test_dummy";
91
92    struct Netns {
93        path: String,
94        _cur: File,
95        last: File,
96    }
97
98    impl Netns {
99        async fn new(path: &str) -> Self {
100            // record current ns
101            let last = File::open(Path::new(SELF_NS_PATH)).unwrap();
102
103            // create new ns
104            NetworkNamespace::add(path.to_string()).await.unwrap();
105
106            // entry new ns
107            let ns_path = Path::new(NETNS_PATH);
108            let file = File::open(ns_path.join(path)).unwrap();
109            setns(file.as_raw_fd(), CloneFlags::CLONE_NEWNET).unwrap();
110
111            Self {
112                path: path.to_string(),
113                _cur: file,
114                last,
115            }
116        }
117    }
118    impl Drop for Netns {
119        fn drop(&mut self) {
120            println!("exit ns: {}", self.path);
121            setns(self.last.as_raw_fd(), CloneFlags::CLONE_NEWNET).unwrap();
122
123            let ns_path = Path::new(NETNS_PATH).join(&self.path);
124            nix::mount::umount2(&ns_path, nix::mount::MntFlags::MNT_DETACH)
125                .unwrap();
126            nix::unistd::unlink(&ns_path).unwrap();
127            // _cur File will be closed auto
128            // Since there is no async drop, NetworkNamespace::del cannot be
129            // called here. Dummy interface will be deleted
130            // automatically after netns is deleted.
131        }
132    }
133
134    async fn setup_env() -> (Handle, LinkMessage, Netns) {
135        let netns = Netns::new(TEST_NS).await;
136
137        // Notice: The Handle can only be created after the setns, so that the
138        // Handle is the connection within the new ns.
139        let (connection, handle, _) = new_connection().unwrap();
140        tokio::spawn(connection);
141        handle
142            .link()
143            .add()
144            .dummy(TEST_DUMMY.to_string())
145            .execute()
146            .await
147            .unwrap();
148        let mut links = handle
149            .link()
150            .get()
151            .match_name(TEST_DUMMY.to_string())
152            .execute();
153        let link = links.try_next().await.unwrap();
154        (handle, link.unwrap(), netns)
155    }
156
157    async fn test_async_new_qdisc() {
158        let (handle, test_link, _netns) = setup_env().await;
159        handle
160            .qdisc()
161            .add(test_link.header.index as i32)
162            .ingress()
163            .execute()
164            .await
165            .unwrap();
166        let mut qdiscs_iter = handle
167            .qdisc()
168            .get()
169            .index(test_link.header.index as i32)
170            .ingress()
171            .execute();
172
173        let mut found = false;
174        while let Some(nl_msg) = qdiscs_iter.try_next().await.unwrap() {
175            if nl_msg.header.index == test_link.header.index as i32
176                && nl_msg.header.handle == 0xffff0000
177            {
178                assert_eq!(nl_msg.header.family, AF_UNSPEC as u8);
179                assert_eq!(nl_msg.header.handle, 0xffff0000);
180                assert_eq!(nl_msg.header.parent, TC_H_INGRESS);
181                assert_eq!(nl_msg.header.info, 1); // refcount
182                assert_eq!(nl_msg.nlas[0], Kind("ingress".to_string()));
183                assert_eq!(nl_msg.nlas[2], HwOffload(0));
184                found = true;
185                break;
186            }
187        }
188        if !found {
189            panic!("not found dev:{} qdisc.", test_link.header.index);
190        }
191    }
192
193    #[test]
194    fn test_new_qdisc() {
195        Runtime::new().unwrap().block_on(test_async_new_qdisc());
196    }
197}