rtnetlink/traffic_control/
add_qdisc.rs

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