1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
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/20210512103451.989420-3-memxor@gmail.com/).
///
/// 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
}
}