1 #[allow(dead_code)]
2 mod common;
3
4 use std::os::unix::io::AsFd as _;
5 use std::os::unix::io::BorrowedFd;
6
7 use serial_test::serial;
8 use test_tag::tag;
9
10 use libbpf_rs::ErrorKind;
11 use libbpf_rs::Result;
12 use libbpf_rs::TcHook;
13 use libbpf_rs::TcHookBuilder;
14 use libbpf_rs::TC_CUSTOM;
15 use libbpf_rs::TC_EGRESS;
16 use libbpf_rs::TC_H_CLSACT;
17 use libbpf_rs::TC_H_MIN_EGRESS;
18 use libbpf_rs::TC_H_MIN_INGRESS;
19 use libbpf_rs::TC_INGRESS;
20
21 use crate::common::bump_rlimit_mlock;
22 use crate::common::get_prog_mut;
23 use crate::common::get_test_object;
24
25 // do all TC tests on the lo network interface
26 const LO_IFINDEX: i32 = 1;
27
28
clear_clsact(fd: BorrowedFd) -> Result<()>29 fn clear_clsact(fd: BorrowedFd) -> Result<()> {
30 // Ensure clean clsact tc qdisc
31 let mut destroyer = TcHook::new(fd);
32 destroyer
33 .ifindex(LO_IFINDEX)
34 .attach_point(TC_EGRESS | TC_INGRESS);
35
36 let res = destroyer.destroy();
37 if let Err(err) = &res {
38 if !matches!(err.kind(), ErrorKind::NotFound | ErrorKind::InvalidInput) {
39 return res;
40 }
41 }
42
43 Ok(())
44 }
45
46 #[tag(root)]
47 #[test]
48 #[serial]
test_tc_basic_cycle()49 fn test_tc_basic_cycle() {
50 bump_rlimit_mlock();
51
52 let mut obj = get_test_object("tc-unit.bpf.o");
53 let prog = get_prog_mut(&mut obj, "handle_tc");
54 let fd = prog.as_fd();
55
56 let mut tc_builder = TcHookBuilder::new(fd);
57 tc_builder
58 .ifindex(LO_IFINDEX)
59 .replace(true)
60 .handle(1)
61 .priority(1);
62 //assert!(!destroyer.destroy().is_err());
63 assert!(clear_clsact(fd).is_ok());
64
65 let mut egress = tc_builder.hook(TC_EGRESS);
66 assert!(egress.create().is_ok());
67 assert!(egress.attach().is_ok());
68 assert!(egress.query().is_ok());
69 assert!(egress.detach().is_ok());
70 assert!(egress.destroy().is_ok());
71 assert!(clear_clsact(fd).is_ok());
72
73 let mut ingress = tc_builder.hook(TC_EGRESS);
74 assert!(ingress.create().is_ok());
75 assert!(ingress.attach().is_ok());
76 assert!(ingress.query().is_ok());
77 assert!(ingress.detach().is_ok());
78 assert!(ingress.destroy().is_ok());
79 assert!(clear_clsact(fd).is_ok());
80
81 let mut custom = tc_builder.hook(TC_CUSTOM);
82 custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS);
83 assert!(ingress.create().is_ok());
84 assert!(custom.attach().is_ok());
85 assert!(custom.query().is_ok());
86 assert!(custom.detach().is_ok());
87 assert!(clear_clsact(fd).is_ok());
88 }
89
90 #[tag(root)]
91 #[test]
92 #[serial]
test_tc_attach_no_qdisc()93 fn test_tc_attach_no_qdisc() {
94 bump_rlimit_mlock();
95
96 let mut obj = get_test_object("tc-unit.bpf.o");
97 let prog = get_prog_mut(&mut obj, "handle_tc");
98 let fd = prog.as_fd();
99
100 let mut tc_builder = TcHookBuilder::new(fd);
101 tc_builder
102 .ifindex(LO_IFINDEX)
103 .replace(true)
104 .handle(1)
105 .priority(1);
106 assert!(clear_clsact(fd).is_ok());
107
108 let mut egress = tc_builder.hook(TC_EGRESS);
109 let mut ingress = tc_builder.hook(TC_INGRESS);
110 let mut custom = tc_builder.hook(TC_CUSTOM);
111
112 assert!(egress.attach().is_err());
113 assert!(ingress.attach().is_err());
114 assert!(custom.attach().is_err());
115 }
116
117 #[tag(root)]
118 #[test]
119 #[serial]
test_tc_attach_basic()120 fn test_tc_attach_basic() {
121 bump_rlimit_mlock();
122
123 let mut obj = get_test_object("tc-unit.bpf.o");
124 let prog = get_prog_mut(&mut obj, "handle_tc");
125 let fd = prog.as_fd();
126
127 let mut tc_builder = TcHookBuilder::new(fd);
128 tc_builder
129 .ifindex(LO_IFINDEX)
130 .replace(true)
131 .handle(1)
132 .priority(1);
133 assert!(clear_clsact(fd).is_ok());
134
135 let mut egress = tc_builder.hook(TC_EGRESS);
136 assert!(egress.attach().is_err());
137 assert!(egress.create().is_ok());
138 assert!(egress.attach().is_ok());
139 assert!(clear_clsact(fd).is_ok());
140
141 let mut ingress = tc_builder.hook(TC_INGRESS);
142 assert!(ingress.attach().is_err());
143 assert!(ingress.create().is_ok());
144 assert!(ingress.attach().is_ok());
145 assert!(clear_clsact(fd).is_ok());
146 }
147
148 #[tag(root)]
149 #[test]
150 #[serial]
test_tc_attach_repeat()151 fn test_tc_attach_repeat() {
152 bump_rlimit_mlock();
153
154 let mut obj = get_test_object("tc-unit.bpf.o");
155 let prog = get_prog_mut(&mut obj, "handle_tc");
156 let fd = prog.as_fd();
157
158 let mut tc_builder = TcHookBuilder::new(fd);
159 tc_builder
160 .ifindex(LO_IFINDEX)
161 .replace(true)
162 .handle(1)
163 .priority(1);
164 assert!(clear_clsact(fd).is_ok());
165
166 let mut egress = tc_builder.hook(TC_EGRESS);
167 assert!(egress.create().is_ok());
168 for _ in 0..10 {
169 assert!(egress.attach().is_ok());
170 }
171
172 let mut ingress = tc_builder.hook(TC_INGRESS);
173 for _ in 0..10 {
174 assert!(ingress.attach().is_ok());
175 }
176
177 let mut custom = tc_builder.hook(TC_CUSTOM);
178 custom.parent(TC_H_CLSACT, TC_H_MIN_EGRESS);
179 for _ in 0..10 {
180 assert!(custom.attach().is_ok());
181 }
182 custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS);
183 for _ in 0..10 {
184 assert!(custom.attach().is_ok());
185 }
186
187 assert!(clear_clsact(fd).is_ok());
188 }
189
190 #[tag(root)]
191 #[test]
192 #[serial]
test_tc_attach_custom()193 fn test_tc_attach_custom() {
194 bump_rlimit_mlock();
195 let mut obj = get_test_object("tc-unit.bpf.o");
196 let prog = get_prog_mut(&mut obj, "handle_tc");
197 let fd = prog.as_fd();
198
199 let mut tc_builder = TcHookBuilder::new(fd);
200 tc_builder
201 .ifindex(LO_IFINDEX)
202 .replace(true)
203 .handle(1)
204 .priority(1);
205 assert!(clear_clsact(fd).is_ok());
206
207 // destroy() ensures that clsact tc qdisc does not exist
208 // but BPF hooks need this qdisc in order to attach
209 // for ingress and egress hooks, the create() method will
210 // ensure that clsact tc qdisc is available, but custom hooks
211 // cannot call create(), thus we need to utilize an ingress, egress, or
212 // egress|ingress hook to create() and ensure
213 // the clsact tc qdisc is available
214
215 let mut custom = tc_builder.hook(TC_CUSTOM);
216 custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS);
217 assert!(custom.attach().is_err());
218 assert!(custom.create().is_err());
219
220 let mut ingress_for_parent = tc_builder.hook(TC_INGRESS);
221 assert!(ingress_for_parent.create().is_ok());
222 assert!(custom.attach().is_ok());
223 assert!(clear_clsact(fd).is_ok());
224 assert!(custom.attach().is_err());
225
226 custom.parent(TC_H_CLSACT, TC_H_MIN_EGRESS);
227 assert!(ingress_for_parent.create().is_ok());
228 assert!(custom.attach().is_ok());
229 assert!(clear_clsact(fd).is_ok());
230 assert!(custom.attach().is_err());
231
232 let mut egress_for_parent = tc_builder.hook(TC_EGRESS);
233 assert!(egress_for_parent.create().is_ok());
234 assert!(custom.attach().is_ok());
235 assert!(clear_clsact(fd).is_ok());
236 assert!(custom.attach().is_err());
237
238 custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS);
239 assert!(egress_for_parent.create().is_ok());
240 assert!(custom.attach().is_ok());
241 assert!(clear_clsact(fd).is_ok());
242 assert!(custom.attach().is_err());
243 }
244
245 #[tag(root)]
246 #[test]
247 #[serial]
test_tc_detach_basic()248 fn test_tc_detach_basic() {
249 bump_rlimit_mlock();
250 let mut obj = get_test_object("tc-unit.bpf.o");
251 let prog = get_prog_mut(&mut obj, "handle_tc");
252 let fd = prog.as_fd();
253
254 let mut tc_builder = TcHookBuilder::new(fd);
255 tc_builder
256 .ifindex(LO_IFINDEX)
257 .replace(true)
258 .handle(1)
259 .priority(1);
260 assert!(clear_clsact(fd).is_ok());
261
262 let mut egress = tc_builder.hook(TC_EGRESS);
263 let mut ingress = tc_builder.hook(TC_INGRESS);
264 let mut custom = tc_builder.hook(TC_CUSTOM);
265 custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS);
266 custom.handle(2);
267
268 assert!(egress.create().is_ok());
269 assert!(egress.attach().is_ok());
270 assert!(ingress.attach().is_ok());
271 assert!(custom.attach().is_ok());
272
273 assert!(egress.detach().is_ok());
274 assert!(ingress.detach().is_ok());
275 assert!(custom.detach().is_ok());
276
277 // test for double detach, error is ENOENT
278 let is_enoent = |hook: &mut TcHook| {
279 if let Err(err) = hook.detach() {
280 err.kind() == ErrorKind::NotFound
281 } else {
282 false
283 }
284 };
285
286 assert!(is_enoent(&mut egress));
287 assert!(is_enoent(&mut ingress));
288 assert!(is_enoent(&mut custom));
289
290 assert!(clear_clsact(fd).is_ok());
291 }
292
293 #[tag(root)]
294 #[test]
295 #[serial]
test_tc_query()296 fn test_tc_query() {
297 bump_rlimit_mlock();
298
299 let mut obj = get_test_object("tc-unit.bpf.o");
300 let prog = get_prog_mut(&mut obj, "handle_tc");
301 let fd = prog.as_fd();
302
303 let mut tc_builder = TcHookBuilder::new(fd);
304 tc_builder
305 .ifindex(LO_IFINDEX)
306 .replace(true)
307 .handle(1)
308 .priority(1);
309 assert!(clear_clsact(fd).is_ok());
310
311 let mut egress = tc_builder.hook(TC_EGRESS);
312 assert!(egress.create().is_ok());
313 assert!(egress.attach().is_ok());
314 assert!(egress.query().is_ok());
315
316 assert!(egress.detach().is_ok());
317 assert!(egress.query().is_err());
318
319 assert!(egress.attach().is_ok());
320 assert!(egress.query().is_ok());
321
322 assert!(egress.destroy().is_ok());
323 assert!(egress.query().is_err());
324
325 assert!(egress.attach().is_ok());
326 assert!(egress.query().is_ok());
327
328 assert!(clear_clsact(fd).is_ok());
329 assert!(egress.query().is_err());
330
331 let mut ingress = tc_builder.hook(TC_INGRESS);
332 assert!(ingress.create().is_ok());
333 assert!(ingress.attach().is_ok());
334 assert!(ingress.query().is_ok());
335
336 assert!(ingress.detach().is_ok());
337 assert!(ingress.query().is_err());
338
339 assert!(ingress.attach().is_ok());
340 assert!(ingress.query().is_ok());
341
342 assert!(ingress.destroy().is_ok());
343 assert!(ingress.query().is_err());
344
345 assert!(ingress.attach().is_ok());
346 assert!(ingress.query().is_ok());
347
348 assert!(clear_clsact(fd).is_ok());
349 assert!(ingress.query().is_err());
350
351 let mut custom = tc_builder.hook(TC_CUSTOM);
352 custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS);
353 assert!(ingress.create().is_ok());
354 assert!(custom.attach().is_ok());
355 assert!(custom.query().is_ok());
356
357 assert!(custom.detach().is_ok());
358 assert!(custom.query().is_err());
359
360 assert!(custom.attach().is_ok());
361 assert!(custom.query().is_ok());
362
363 assert!(clear_clsact(fd).is_ok());
364 assert!(custom.query().is_err());
365 }
366
367 #[tag(root)]
368 #[test]
369 #[serial]
test_tc_double_create()370 fn test_tc_double_create() {
371 bump_rlimit_mlock();
372
373 let mut obj = get_test_object("tc-unit.bpf.o");
374 let prog = get_prog_mut(&mut obj, "handle_tc");
375 let fd = prog.as_fd();
376
377 let mut tc_builder = TcHookBuilder::new(fd);
378 tc_builder
379 .ifindex(LO_IFINDEX)
380 .replace(true)
381 .handle(1)
382 .priority(1);
383 assert!(clear_clsact(fd).is_ok());
384
385 let mut ingress = tc_builder.hook(TC_INGRESS);
386 let mut egress = tc_builder.hook(TC_EGRESS);
387
388 assert!(ingress.create().is_ok());
389 assert!(egress.create().is_ok());
390
391 assert!(clear_clsact(fd).is_ok());
392 }
393