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