rtnetlink/traffic_control/
add_qdisc.rs

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