| #[allow(dead_code)] | |
| mod common; | |
| use std::os::unix::io::AsFd as _; | |
| use std::os::unix::io::BorrowedFd; | |
| use serial_test::serial; | |
| use test_tag::tag; | |
| use libbpf_rs::ErrorKind; | |
| use libbpf_rs::Result; | |
| use libbpf_rs::TcHook; | |
| use libbpf_rs::TcHookBuilder; | |
| use libbpf_rs::TC_CUSTOM; | |
| use libbpf_rs::TC_EGRESS; | |
| use libbpf_rs::TC_H_CLSACT; | |
| use libbpf_rs::TC_H_MIN_EGRESS; | |
| use libbpf_rs::TC_H_MIN_INGRESS; | |
| use libbpf_rs::TC_INGRESS; | |
| use crate::common::bump_rlimit_mlock; | |
| use crate::common::get_prog_mut; | |
| use crate::common::get_test_object; | |
| // do all TC tests on the lo network interface | |
| const LO_IFINDEX: i32 = 1; | |
| fn clear_clsact(fd: BorrowedFd) -> Result<()> { | |
| // Ensure clean clsact tc qdisc | |
| let mut destroyer = TcHook::new(fd); | |
| destroyer | |
| .ifindex(LO_IFINDEX) | |
| .attach_point(TC_EGRESS | TC_INGRESS); | |
| let res = destroyer.destroy(); | |
| if let Err(err) = &res { | |
| if !matches!(err.kind(), ErrorKind::NotFound | ErrorKind::InvalidInput) { | |
| return res; | |
| } | |
| } | |
| Ok(()) | |
| } | |
| #[tag(root)] | |
| #[test] | |
| #[serial] | |
| fn test_tc_basic_cycle() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tc-unit.bpf.o"); | |
| let prog = get_prog_mut(&mut obj, "handle_tc"); | |
| let fd = prog.as_fd(); | |
| let mut tc_builder = TcHookBuilder::new(fd); | |
| tc_builder | |
| .ifindex(LO_IFINDEX) | |
| .replace(true) | |
| .handle(1) | |
| .priority(1); | |
| //assert!(!destroyer.destroy().is_err()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| let mut egress = tc_builder.hook(TC_EGRESS); | |
| assert!(egress.create().is_ok()); | |
| assert!(egress.attach().is_ok()); | |
| assert!(egress.query().is_ok()); | |
| assert!(egress.detach().is_ok()); | |
| assert!(egress.destroy().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| let mut ingress = tc_builder.hook(TC_EGRESS); | |
| assert!(ingress.create().is_ok()); | |
| assert!(ingress.attach().is_ok()); | |
| assert!(ingress.query().is_ok()); | |
| assert!(ingress.detach().is_ok()); | |
| assert!(ingress.destroy().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| let mut custom = tc_builder.hook(TC_CUSTOM); | |
| custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS); | |
| assert!(ingress.create().is_ok()); | |
| assert!(custom.attach().is_ok()); | |
| assert!(custom.query().is_ok()); | |
| assert!(custom.detach().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| #[serial] | |
| fn test_tc_attach_no_qdisc() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tc-unit.bpf.o"); | |
| let prog = get_prog_mut(&mut obj, "handle_tc"); | |
| let fd = prog.as_fd(); | |
| let mut tc_builder = TcHookBuilder::new(fd); | |
| tc_builder | |
| .ifindex(LO_IFINDEX) | |
| .replace(true) | |
| .handle(1) | |
| .priority(1); | |
| assert!(clear_clsact(fd).is_ok()); | |
| let mut egress = tc_builder.hook(TC_EGRESS); | |
| let mut ingress = tc_builder.hook(TC_INGRESS); | |
| let mut custom = tc_builder.hook(TC_CUSTOM); | |
| assert!(egress.attach().is_err()); | |
| assert!(ingress.attach().is_err()); | |
| assert!(custom.attach().is_err()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| #[serial] | |
| fn test_tc_attach_basic() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tc-unit.bpf.o"); | |
| let prog = get_prog_mut(&mut obj, "handle_tc"); | |
| let fd = prog.as_fd(); | |
| let mut tc_builder = TcHookBuilder::new(fd); | |
| tc_builder | |
| .ifindex(LO_IFINDEX) | |
| .replace(true) | |
| .handle(1) | |
| .priority(1); | |
| assert!(clear_clsact(fd).is_ok()); | |
| let mut egress = tc_builder.hook(TC_EGRESS); | |
| assert!(egress.attach().is_err()); | |
| assert!(egress.create().is_ok()); | |
| assert!(egress.attach().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| let mut ingress = tc_builder.hook(TC_INGRESS); | |
| assert!(ingress.attach().is_err()); | |
| assert!(ingress.create().is_ok()); | |
| assert!(ingress.attach().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| #[serial] | |
| fn test_tc_attach_repeat() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tc-unit.bpf.o"); | |
| let prog = get_prog_mut(&mut obj, "handle_tc"); | |
| let fd = prog.as_fd(); | |
| let mut tc_builder = TcHookBuilder::new(fd); | |
| tc_builder | |
| .ifindex(LO_IFINDEX) | |
| .replace(true) | |
| .handle(1) | |
| .priority(1); | |
| assert!(clear_clsact(fd).is_ok()); | |
| let mut egress = tc_builder.hook(TC_EGRESS); | |
| assert!(egress.create().is_ok()); | |
| for _ in 0..10 { | |
| assert!(egress.attach().is_ok()); | |
| } | |
| let mut ingress = tc_builder.hook(TC_INGRESS); | |
| for _ in 0..10 { | |
| assert!(ingress.attach().is_ok()); | |
| } | |
| let mut custom = tc_builder.hook(TC_CUSTOM); | |
| custom.parent(TC_H_CLSACT, TC_H_MIN_EGRESS); | |
| for _ in 0..10 { | |
| assert!(custom.attach().is_ok()); | |
| } | |
| custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS); | |
| for _ in 0..10 { | |
| assert!(custom.attach().is_ok()); | |
| } | |
| assert!(clear_clsact(fd).is_ok()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| #[serial] | |
| fn test_tc_attach_custom() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tc-unit.bpf.o"); | |
| let prog = get_prog_mut(&mut obj, "handle_tc"); | |
| let fd = prog.as_fd(); | |
| let mut tc_builder = TcHookBuilder::new(fd); | |
| tc_builder | |
| .ifindex(LO_IFINDEX) | |
| .replace(true) | |
| .handle(1) | |
| .priority(1); | |
| assert!(clear_clsact(fd).is_ok()); | |
| // destroy() ensures that clsact tc qdisc does not exist | |
| // but BPF hooks need this qdisc in order to attach | |
| // for ingress and egress hooks, the create() method will | |
| // ensure that clsact tc qdisc is available, but custom hooks | |
| // cannot call create(), thus we need to utilize an ingress, egress, or | |
| // egress|ingress hook to create() and ensure | |
| // the clsact tc qdisc is available | |
| let mut custom = tc_builder.hook(TC_CUSTOM); | |
| custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS); | |
| assert!(custom.attach().is_err()); | |
| assert!(custom.create().is_err()); | |
| let mut ingress_for_parent = tc_builder.hook(TC_INGRESS); | |
| assert!(ingress_for_parent.create().is_ok()); | |
| assert!(custom.attach().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| assert!(custom.attach().is_err()); | |
| custom.parent(TC_H_CLSACT, TC_H_MIN_EGRESS); | |
| assert!(ingress_for_parent.create().is_ok()); | |
| assert!(custom.attach().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| assert!(custom.attach().is_err()); | |
| let mut egress_for_parent = tc_builder.hook(TC_EGRESS); | |
| assert!(egress_for_parent.create().is_ok()); | |
| assert!(custom.attach().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| assert!(custom.attach().is_err()); | |
| custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS); | |
| assert!(egress_for_parent.create().is_ok()); | |
| assert!(custom.attach().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| assert!(custom.attach().is_err()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| #[serial] | |
| fn test_tc_detach_basic() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tc-unit.bpf.o"); | |
| let prog = get_prog_mut(&mut obj, "handle_tc"); | |
| let fd = prog.as_fd(); | |
| let mut tc_builder = TcHookBuilder::new(fd); | |
| tc_builder | |
| .ifindex(LO_IFINDEX) | |
| .replace(true) | |
| .handle(1) | |
| .priority(1); | |
| assert!(clear_clsact(fd).is_ok()); | |
| let mut egress = tc_builder.hook(TC_EGRESS); | |
| let mut ingress = tc_builder.hook(TC_INGRESS); | |
| let mut custom = tc_builder.hook(TC_CUSTOM); | |
| custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS); | |
| custom.handle(2); | |
| assert!(egress.create().is_ok()); | |
| assert!(egress.attach().is_ok()); | |
| assert!(ingress.attach().is_ok()); | |
| assert!(custom.attach().is_ok()); | |
| assert!(egress.detach().is_ok()); | |
| assert!(ingress.detach().is_ok()); | |
| assert!(custom.detach().is_ok()); | |
| // test for double detach, error is ENOENT | |
| let is_enoent = |hook: &mut TcHook| { | |
| if let Err(err) = hook.detach() { | |
| err.kind() == ErrorKind::NotFound | |
| } else { | |
| false | |
| } | |
| }; | |
| assert!(is_enoent(&mut egress)); | |
| assert!(is_enoent(&mut ingress)); | |
| assert!(is_enoent(&mut custom)); | |
| assert!(clear_clsact(fd).is_ok()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| #[serial] | |
| fn test_tc_query() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tc-unit.bpf.o"); | |
| let prog = get_prog_mut(&mut obj, "handle_tc"); | |
| let fd = prog.as_fd(); | |
| let mut tc_builder = TcHookBuilder::new(fd); | |
| tc_builder | |
| .ifindex(LO_IFINDEX) | |
| .replace(true) | |
| .handle(1) | |
| .priority(1); | |
| assert!(clear_clsact(fd).is_ok()); | |
| let mut egress = tc_builder.hook(TC_EGRESS); | |
| assert!(egress.create().is_ok()); | |
| assert!(egress.attach().is_ok()); | |
| assert!(egress.query().is_ok()); | |
| assert!(egress.detach().is_ok()); | |
| assert!(egress.query().is_err()); | |
| assert!(egress.attach().is_ok()); | |
| assert!(egress.query().is_ok()); | |
| assert!(egress.destroy().is_ok()); | |
| assert!(egress.query().is_err()); | |
| assert!(egress.attach().is_ok()); | |
| assert!(egress.query().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| assert!(egress.query().is_err()); | |
| let mut ingress = tc_builder.hook(TC_INGRESS); | |
| assert!(ingress.create().is_ok()); | |
| assert!(ingress.attach().is_ok()); | |
| assert!(ingress.query().is_ok()); | |
| assert!(ingress.detach().is_ok()); | |
| assert!(ingress.query().is_err()); | |
| assert!(ingress.attach().is_ok()); | |
| assert!(ingress.query().is_ok()); | |
| assert!(ingress.destroy().is_ok()); | |
| assert!(ingress.query().is_err()); | |
| assert!(ingress.attach().is_ok()); | |
| assert!(ingress.query().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| assert!(ingress.query().is_err()); | |
| let mut custom = tc_builder.hook(TC_CUSTOM); | |
| custom.parent(TC_H_CLSACT, TC_H_MIN_INGRESS); | |
| assert!(ingress.create().is_ok()); | |
| assert!(custom.attach().is_ok()); | |
| assert!(custom.query().is_ok()); | |
| assert!(custom.detach().is_ok()); | |
| assert!(custom.query().is_err()); | |
| assert!(custom.attach().is_ok()); | |
| assert!(custom.query().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| assert!(custom.query().is_err()); | |
| } | |
| #[tag(root)] | |
| #[test] | |
| #[serial] | |
| fn test_tc_double_create() { | |
| bump_rlimit_mlock(); | |
| let mut obj = get_test_object("tc-unit.bpf.o"); | |
| let prog = get_prog_mut(&mut obj, "handle_tc"); | |
| let fd = prog.as_fd(); | |
| let mut tc_builder = TcHookBuilder::new(fd); | |
| tc_builder | |
| .ifindex(LO_IFINDEX) | |
| .replace(true) | |
| .handle(1) | |
| .priority(1); | |
| assert!(clear_clsact(fd).is_ok()); | |
| let mut ingress = tc_builder.hook(TC_INGRESS); | |
| let mut egress = tc_builder.hook(TC_EGRESS); | |
| assert!(ingress.create().is_ok()); | |
| assert!(egress.create().is_ok()); | |
| assert!(clear_clsact(fd).is_ok()); | |
| } |