rtnetlink/traffic_control/
add_qdisc.rs1use 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 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 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 pub fn root(mut self) -> Self {
55 self.message.header.parent = TC_H_ROOT;
56 self
57 }
58
59 pub fn parent(mut self, parent: u32) -> Self {
61 self.message.header.parent = parent;
62 self
63 }
64
65 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 let last = File::open(Path::new(SELF_NS_PATH)).unwrap();
102
103 NetworkNamespace::add(path.to_string()).await.unwrap();
105
106 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 }
132 }
133
134 async fn setup_env() -> (Handle, LinkMessage, Netns) {
135 let netns = Netns::new(TEST_NS).await;
136
137 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); 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}