rtnetlink/traffic_control/
add_qdisc.rs1use 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 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 pub fn handle(mut self, major: u16, minor: u16) -> Self {
52 self.message.header.handle = TcHandle { major, minor };
53 self
54 }
55
56 pub fn root(mut self) -> Self {
58 self.message.header.parent = TcHandle::ROOT;
59 self
60 }
61
62 pub fn parent(mut self, parent: u32) -> Self {
64 self.message.header.parent = parent.into();
65 self
66 }
67
68 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 let last = File::open(Path::new(SELF_NS_PATH)).unwrap();
107
108 NetworkNamespace::add(path.to_string()).await.unwrap();
110
111 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 }
137 }
138
139 async fn setup_env() -> (Handle, LinkMessage, Netns) {
140 let netns = Netns::new(TEST_NS).await;
141
142 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); 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}