rtnetlink/traffic_control/
add_qdisc.rs1use 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 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 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 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 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 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 let last = File::open(Path::new(SELF_NS_PATH)).unwrap();
119
120 NetworkNamespace::add(path.to_string()).await.unwrap();
122
123 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 }
148 }
149
150 async fn setup_env() -> (Handle, LinkMessage, Netns) {
151 let netns = Netns::new(TEST_NS).await;
152
153 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); 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}