| use std::io; | |
| use std::mem::size_of; | |
| use std::os::unix::io::AsRawFd; | |
| use std::os::unix::io::BorrowedFd; | |
| use crate::Error; | |
| use crate::Result; | |
| /// See [`libbpf_sys::bpf_tc_attach_point`]. | |
| pub type TcAttachPoint = libbpf_sys::bpf_tc_attach_point; | |
| /// See [`libbpf_sys::BPF_TC_INGRESS`]. | |
| pub const TC_INGRESS: TcAttachPoint = libbpf_sys::BPF_TC_INGRESS; | |
| /// See [`libbpf_sys::BPF_TC_EGRESS`]. | |
| pub const TC_EGRESS: TcAttachPoint = libbpf_sys::BPF_TC_EGRESS; | |
| /// See [`libbpf_sys::BPF_TC_CUSTOM`]. | |
| pub const TC_CUSTOM: TcAttachPoint = libbpf_sys::BPF_TC_CUSTOM; | |
| pub type TcFlags = libbpf_sys::bpf_tc_flags; | |
| /// See [`libbpf_sys::BPF_TC_F_REPLACE`]. | |
| pub const BPF_TC_F_REPLACE: TcFlags = libbpf_sys::BPF_TC_F_REPLACE; | |
| // from kernel @ include/uapi/linux/pkt_sched.h | |
| #[allow(missing_docs)] | |
| pub const TC_H_INGRESS: u32 = 0xFFFFFFF1; | |
| #[allow(missing_docs)] | |
| pub const TC_H_CLSACT: u32 = TC_H_INGRESS; | |
| #[allow(missing_docs)] | |
| pub const TC_H_MIN_INGRESS: u32 = 0xFFF2; | |
| #[allow(missing_docs)] | |
| pub const TC_H_MIN_EGRESS: u32 = 0xFFF3; | |
| #[allow(missing_docs)] | |
| pub const TC_H_MAJ_MASK: u32 = 0xFFFF0000; | |
| #[allow(missing_docs)] | |
| pub const TC_H_MIN_MASK: u32 = 0x0000FFFF; | |
| /// Represents a location where a TC-BPF filter can be attached. | |
| /// | |
| /// The BPF TC subsystem has different control paths from other BPF programs. | |
| /// As such a BPF program using a TC Hook (`SEC("classifier")` or `SEC("tc")`) must be operated | |
| /// more independently from other [`Program`][crate::Program]s. | |
| /// | |
| /// This struct exposes operations to create, attach, query and destroy | |
| /// a bpf_tc_hook using the TC subsystem. | |
| /// | |
| /// Documentation about the libbpf TC interface can be found | |
| /// [here](https://lwn.net/ml/bpf/[email protected]/). | |
| /// | |
| /// An example of using a BPF TC program can found | |
| /// [here](https://github.com/libbpf/libbpf-rs/tree/master/examples/tc_port_whitelist). | |
| #[derive(Clone, Copy, Debug)] | |
| pub struct TcHook { | |
| hook: libbpf_sys::bpf_tc_hook, | |
| opts: libbpf_sys::bpf_tc_opts, | |
| } | |
| impl TcHook { | |
| /// Create a new [`TcHook`] given the file descriptor of the loaded | |
| /// `SEC("tc")` [`Program`][crate::Program]. | |
| pub fn new(fd: BorrowedFd<'_>) -> Self { | |
| let mut tc_hook = TcHook { | |
| hook: libbpf_sys::bpf_tc_hook::default(), | |
| opts: libbpf_sys::bpf_tc_opts::default(), | |
| }; | |
| tc_hook.hook.sz = size_of::<libbpf_sys::bpf_tc_hook>() as libbpf_sys::size_t; | |
| tc_hook.opts.sz = size_of::<libbpf_sys::bpf_tc_opts>() as libbpf_sys::size_t; | |
| tc_hook.opts.prog_fd = fd.as_raw_fd(); | |
| tc_hook | |
| } | |
| /// Create a new [`TcHook`] as well as the underlying qdiscs | |
| /// | |
| /// If a [`TcHook`] already exists with the same parameters as the hook calling | |
| /// [`Self::create()`], this function will still succeed. | |
| /// | |
| /// Will always fail on a `TC_CUSTOM` hook | |
| pub fn create(&mut self) -> Result<Self> { | |
| let err = unsafe { libbpf_sys::bpf_tc_hook_create(&mut self.hook as *mut _) }; | |
| if err != 0 { | |
| let err = io::Error::from_raw_os_error(-err); | |
| // the hook may already exist, this is not an error | |
| if err.kind() == io::ErrorKind::AlreadyExists { | |
| Ok(*self) | |
| } else { | |
| Err(Error::from(err)) | |
| } | |
| } else { | |
| Ok(*self) | |
| } | |
| } | |
| /// Set the interface to attach to | |
| /// | |
| /// Interfaces can be listed by using `ip link` command from the iproute2 software package | |
| pub fn ifindex(&mut self, idx: i32) -> &mut Self { | |
| self.hook.ifindex = idx; | |
| self | |
| } | |
| /// Set what type of TC point to attach onto | |
| /// | |
| /// `TC_EGRESS`, `TC_INGRESS`, or `TC_CUSTOM` | |
| /// | |
| /// An `TC_EGRESS|TC_INGRESS` hook can be used as an attach point for calling | |
| /// [`Self::destroy()`] to remove the clsact bpf tc qdisc, but cannot be used for an | |
| /// [`Self::attach()`] operation | |
| pub fn attach_point(&mut self, ap: TcAttachPoint) -> &mut Self { | |
| self.hook.attach_point = ap; | |
| self | |
| } | |
| /// Set the parent of a hook | |
| /// | |
| /// Will cause an EINVAL upon [`Self::attach()`] if set upon an | |
| /// `TC_EGRESS/TC_INGRESS/(TC_EGRESS|TC_INGRESS)` hook | |
| /// | |
| /// Must be set on a `TC_CUSTOM` hook | |
| /// | |
| /// Current acceptable values are `TC_H_CLSACT` for `maj`, and `TC_H_MIN_EGRESS` or | |
| /// `TC_H_MIN_INGRESS` for `min` | |
| pub fn parent(&mut self, maj: u32, min: u32) -> &mut Self { | |
| /* values from libbpf.h BPF_TC_PARENT() */ | |
| let parent = (maj & TC_H_MAJ_MASK) | (min & TC_H_MIN_MASK); | |
| self.hook.parent = parent; | |
| self | |
| } | |
| /// Set whether this hook should replace an existing hook | |
| /// | |
| /// If replace is not true upon attach, and a hook already exists | |
| /// an EEXIST error will be returned from [`Self::attach()`] | |
| pub fn replace(&mut self, replace: bool) -> &mut Self { | |
| if replace { | |
| self.opts.flags = BPF_TC_F_REPLACE; | |
| } else { | |
| self.opts.flags = 0; | |
| } | |
| self | |
| } | |
| /// Set the handle of a hook. | |
| /// If unset upon attach, the kernel will assign a handle for the hook | |
| pub fn handle(&mut self, handle: u32) -> &mut Self { | |
| self.opts.handle = handle; | |
| self | |
| } | |
| /// Get the handle of a hook. | |
| /// Only has meaning after hook is attached | |
| pub fn get_handle(&self) -> u32 { | |
| self.opts.handle | |
| } | |
| /// Set the priority of a hook | |
| /// If unset upon attach, the kernel will assign a priority for the hook | |
| pub fn priority(&mut self, priority: u32) -> &mut Self { | |
| self.opts.priority = priority; | |
| self | |
| } | |
| /// Get the priority of a hook | |
| /// Only has meaning after hook is attached | |
| pub fn get_priority(&self) -> u32 { | |
| self.opts.priority | |
| } | |
| /// Query a hook to inspect the program identifier (prog_id) | |
| pub fn query(&mut self) -> Result<u32> { | |
| let mut opts = self.opts; | |
| opts.prog_id = 0; | |
| opts.prog_fd = 0; | |
| opts.flags = 0; | |
| let err = unsafe { libbpf_sys::bpf_tc_query(&self.hook as *const _, &mut opts as *mut _) }; | |
| if err != 0 { | |
| Err(Error::from(io::Error::last_os_error())) | |
| } else { | |
| Ok(opts.prog_id) | |
| } | |
| } | |
| /// Attach a filter to the TcHook so that the program starts processing | |
| /// | |
| /// Once the hook is processing, changing the values will have no effect unless the hook is | |
| /// [`Self::attach()`]'d again (`replace=true` being required) | |
| /// | |
| /// Users can create a second hook by changing the handle, the priority or the attach_point and | |
| /// calling the [`Self::attach()`] method again. Beware doing this. It might be better to | |
| /// Copy the TcHook and change the values on the copied hook for easier [`Self::detach()`] | |
| /// | |
| /// NOTE: Once a [`TcHook`] is attached, it, and the maps it uses, will outlive the userspace | |
| /// application that spawned them Make sure to detach if this is not desired | |
| pub fn attach(&mut self) -> Result<Self> { | |
| self.opts.prog_id = 0; | |
| let err = | |
| unsafe { libbpf_sys::bpf_tc_attach(&self.hook as *const _, &mut self.opts as *mut _) }; | |
| if err != 0 { | |
| Err(Error::from(io::Error::last_os_error())) | |
| } else { | |
| Ok(*self) | |
| } | |
| } | |
| /// Detach a filter from a [`TcHook`] | |
| pub fn detach(&mut self) -> Result<()> { | |
| let mut opts = self.opts; | |
| opts.prog_id = 0; | |
| opts.prog_fd = 0; | |
| opts.flags = 0; | |
| let err = unsafe { libbpf_sys::bpf_tc_detach(&self.hook as *const _, &opts as *const _) }; | |
| if err != 0 { | |
| Err(Error::from_raw_os_error(-err)) | |
| } else { | |
| self.opts.prog_id = 0; | |
| Ok(()) | |
| } | |
| } | |
| /// Destroy attached filters | |
| /// | |
| /// If called on a hook with an attach_point of `TC_EGRESS`, will detach all egress hooks | |
| /// | |
| /// If called on a hook with an attach_point of `TC_INGRESS`, will detach all ingress hooks | |
| /// | |
| /// If called on a hook with an attach_point of `TC_EGRESS|TC_INGRESS`, will destroy the clsact | |
| /// tc qdisc and detach all hooks | |
| /// | |
| /// Will error with EOPNOTSUPP if attach_point is `TC_CUSTOM` | |
| /// | |
| /// It is good practice to query before destroying as the tc qdisc may be used by multiple | |
| /// programs | |
| pub fn destroy(&mut self) -> Result<()> { | |
| let err = unsafe { libbpf_sys::bpf_tc_hook_destroy(&mut self.hook as *mut _) }; | |
| if err != 0 { | |
| Err(Error::from_raw_os_error(-err)) | |
| } else { | |
| Ok(()) | |
| } | |
| } | |
| } | |
| /// Builds [`TcHook`] instances. | |
| /// | |
| /// [`TcHookBuilder`] is a way to ergonomically create multiple `TcHook`s, | |
| /// all with similar initial values. | |
| /// | |
| /// Once a `TcHook` is created via the [`Self::hook()`] method, the `TcHook`'s values can still | |
| /// be adjusted before [`TcHook::attach()`] is called. | |
| #[derive(Debug)] | |
| pub struct TcHookBuilder<'fd> { | |
| fd: BorrowedFd<'fd>, | |
| ifindex: i32, | |
| parent_maj: u32, | |
| parent_min: u32, | |
| replace: bool, | |
| handle: u32, | |
| priority: u32, | |
| } | |
| impl<'fd> TcHookBuilder<'fd> { | |
| /// Create a new `TcHookBuilder` with fd | |
| /// this fd should come from a loaded [`Program`][crate::Program] | |
| pub fn new(fd: BorrowedFd<'fd>) -> Self { | |
| TcHookBuilder { | |
| fd, | |
| ifindex: 0, | |
| parent_maj: 0, | |
| parent_min: 0, | |
| replace: false, | |
| handle: 0, | |
| priority: 0, | |
| } | |
| } | |
| /// Set the initial interface index to attach the hook on | |
| pub fn ifindex(&mut self, ifindex: i32) -> &mut Self { | |
| self.ifindex = ifindex; | |
| self | |
| } | |
| /// Set the initial parent of a hook | |
| pub fn parent(&mut self, maj: u32, min: u32) -> &mut Self { | |
| self.parent_maj = maj; | |
| self.parent_min = min; | |
| self | |
| } | |
| /// Set whether created hooks should replace existing hooks | |
| pub fn replace(&mut self, replace: bool) -> &mut Self { | |
| self.replace = replace; | |
| self | |
| } | |
| /// Set the initial handle for a hook | |
| pub fn handle(&mut self, handle: u32) -> &mut Self { | |
| self.handle = handle; | |
| self | |
| } | |
| /// Set the initial priority for a hook | |
| pub fn priority(&mut self, priority: u32) -> &mut Self { | |
| self.priority = priority; | |
| self | |
| } | |
| /// Create a [`TcHook`] given the values previously set | |
| /// | |
| /// Once a hook is created, the values can still be changed on the `TcHook` | |
| /// by calling the `TcHooks` setter methods | |
| pub fn hook(&self, attach_point: TcAttachPoint) -> TcHook { | |
| let mut hook = TcHook::new(self.fd); | |
| hook.ifindex(self.ifindex) | |
| .handle(self.handle) | |
| .priority(self.priority) | |
| .parent(self.parent_maj, self.parent_min) | |
| .replace(self.replace) | |
| .attach_point(attach_point); | |
| hook | |
| } | |
| } |