libbpf_rs/tc.rs
1use std::io;
2use std::mem::size_of;
3use std::os::unix::io::AsRawFd;
4use std::os::unix::io::BorrowedFd;
5
6use crate::Error;
7use crate::Result;
8
9/// See [`libbpf_sys::bpf_tc_attach_point`].
10pub type TcAttachPoint = libbpf_sys::bpf_tc_attach_point;
11/// See [`libbpf_sys::BPF_TC_INGRESS`].
12pub const TC_INGRESS: TcAttachPoint = libbpf_sys::BPF_TC_INGRESS;
13/// See [`libbpf_sys::BPF_TC_EGRESS`].
14pub const TC_EGRESS: TcAttachPoint = libbpf_sys::BPF_TC_EGRESS;
15/// See [`libbpf_sys::BPF_TC_CUSTOM`].
16pub const TC_CUSTOM: TcAttachPoint = libbpf_sys::BPF_TC_CUSTOM;
17
18pub type TcFlags = libbpf_sys::bpf_tc_flags;
19/// See [`libbpf_sys::BPF_TC_F_REPLACE`].
20pub const BPF_TC_F_REPLACE: TcFlags = libbpf_sys::BPF_TC_F_REPLACE;
21
22// from kernel @ include/uapi/linux/pkt_sched.h
23#[allow(missing_docs)]
24pub const TC_H_INGRESS: u32 = 0xFFFFFFF1;
25#[allow(missing_docs)]
26pub const TC_H_CLSACT: u32 = TC_H_INGRESS;
27#[allow(missing_docs)]
28pub const TC_H_MIN_INGRESS: u32 = 0xFFF2;
29#[allow(missing_docs)]
30pub const TC_H_MIN_EGRESS: u32 = 0xFFF3;
31#[allow(missing_docs)]
32pub const TC_H_MAJ_MASK: u32 = 0xFFFF0000;
33#[allow(missing_docs)]
34pub const TC_H_MIN_MASK: u32 = 0x0000FFFF;
35
36/// Represents a location where a TC-BPF filter can be attached.
37///
38/// The BPF TC subsystem has different control paths from other BPF programs.
39/// As such a BPF program using a TC Hook (`SEC("classifier")` or `SEC("tc")`) must be operated
40/// more independently from other [`Program`][crate::Program]s.
41///
42/// This struct exposes operations to create, attach, query and destroy
43/// a bpf_tc_hook using the TC subsystem.
44///
45/// Documentation about the libbpf TC interface can be found
46/// [here](https://lwn.net/ml/bpf/20210512103451.989420-3-memxor@gmail.com/).
47///
48/// An example of using a BPF TC program can found
49/// [here](https://github.com/libbpf/libbpf-rs/tree/master/examples/tc_port_whitelist).
50#[derive(Clone, Copy, Debug)]
51pub struct TcHook {
52 hook: libbpf_sys::bpf_tc_hook,
53 opts: libbpf_sys::bpf_tc_opts,
54}
55
56impl TcHook {
57 /// Create a new [`TcHook`] given the file descriptor of the loaded
58 /// `SEC("tc")` [`Program`][crate::Program].
59 pub fn new(fd: BorrowedFd<'_>) -> Self {
60 let mut tc_hook = TcHook {
61 hook: libbpf_sys::bpf_tc_hook::default(),
62 opts: libbpf_sys::bpf_tc_opts::default(),
63 };
64
65 tc_hook.hook.sz = size_of::<libbpf_sys::bpf_tc_hook>() as libbpf_sys::size_t;
66 tc_hook.opts.sz = size_of::<libbpf_sys::bpf_tc_opts>() as libbpf_sys::size_t;
67 tc_hook.opts.prog_fd = fd.as_raw_fd();
68
69 tc_hook
70 }
71
72 /// Create a new [`TcHook`] as well as the underlying qdiscs
73 ///
74 /// If a [`TcHook`] already exists with the same parameters as the hook calling
75 /// [`Self::create()`], this function will still succeed.
76 ///
77 /// Will always fail on a `TC_CUSTOM` hook
78 pub fn create(&mut self) -> Result<Self> {
79 let err = unsafe { libbpf_sys::bpf_tc_hook_create(&mut self.hook as *mut _) };
80 if err != 0 {
81 let err = io::Error::from_raw_os_error(-err);
82 // the hook may already exist, this is not an error
83 if err.kind() == io::ErrorKind::AlreadyExists {
84 Ok(*self)
85 } else {
86 Err(Error::from(err))
87 }
88 } else {
89 Ok(*self)
90 }
91 }
92
93 /// Set the interface to attach to
94 ///
95 /// Interfaces can be listed by using `ip link` command from the iproute2 software package
96 pub fn ifindex(&mut self, idx: i32) -> &mut Self {
97 self.hook.ifindex = idx;
98 self
99 }
100
101 /// Set what type of TC point to attach onto
102 ///
103 /// `TC_EGRESS`, `TC_INGRESS`, or `TC_CUSTOM`
104 ///
105 /// An `TC_EGRESS|TC_INGRESS` hook can be used as an attach point for calling
106 /// [`Self::destroy()`] to remove the clsact bpf tc qdisc, but cannot be used for an
107 /// [`Self::attach()`] operation
108 pub fn attach_point(&mut self, ap: TcAttachPoint) -> &mut Self {
109 self.hook.attach_point = ap;
110 self
111 }
112
113 /// Set the parent of a hook
114 ///
115 /// Will cause an EINVAL upon [`Self::attach()`] if set upon an
116 /// `TC_EGRESS/TC_INGRESS/(TC_EGRESS|TC_INGRESS)` hook
117 ///
118 /// Must be set on a `TC_CUSTOM` hook
119 ///
120 /// Current acceptable values are `TC_H_CLSACT` for `maj`, and `TC_H_MIN_EGRESS` or
121 /// `TC_H_MIN_INGRESS` for `min`
122 pub fn parent(&mut self, maj: u32, min: u32) -> &mut Self {
123 /* values from libbpf.h BPF_TC_PARENT() */
124 let parent = (maj & TC_H_MAJ_MASK) | (min & TC_H_MIN_MASK);
125 self.hook.parent = parent;
126 self
127 }
128
129 /// Set whether this hook should replace an existing hook
130 ///
131 /// If replace is not true upon attach, and a hook already exists
132 /// an EEXIST error will be returned from [`Self::attach()`]
133 pub fn replace(&mut self, replace: bool) -> &mut Self {
134 if replace {
135 self.opts.flags = BPF_TC_F_REPLACE;
136 } else {
137 self.opts.flags = 0;
138 }
139 self
140 }
141
142 /// Set the handle of a hook.
143 /// If unset upon attach, the kernel will assign a handle for the hook
144 pub fn handle(&mut self, handle: u32) -> &mut Self {
145 self.opts.handle = handle;
146 self
147 }
148
149 /// Get the handle of a hook.
150 /// Only has meaning after hook is attached
151 pub fn get_handle(&self) -> u32 {
152 self.opts.handle
153 }
154
155 /// Set the priority of a hook
156 /// If unset upon attach, the kernel will assign a priority for the hook
157 pub fn priority(&mut self, priority: u32) -> &mut Self {
158 self.opts.priority = priority;
159 self
160 }
161
162 /// Get the priority of a hook
163 /// Only has meaning after hook is attached
164 pub fn get_priority(&self) -> u32 {
165 self.opts.priority
166 }
167
168 /// Query a hook to inspect the program identifier (prog_id)
169 pub fn query(&mut self) -> Result<u32> {
170 let mut opts = self.opts;
171 opts.prog_id = 0;
172 opts.prog_fd = 0;
173 opts.flags = 0;
174
175 let err = unsafe { libbpf_sys::bpf_tc_query(&self.hook as *const _, &mut opts as *mut _) };
176 if err != 0 {
177 Err(Error::from(io::Error::last_os_error()))
178 } else {
179 Ok(opts.prog_id)
180 }
181 }
182
183 /// Attach a filter to the TcHook so that the program starts processing
184 ///
185 /// Once the hook is processing, changing the values will have no effect unless the hook is
186 /// [`Self::attach()`]'d again (`replace=true` being required)
187 ///
188 /// Users can create a second hook by changing the handle, the priority or the attach_point and
189 /// calling the [`Self::attach()`] method again. Beware doing this. It might be better to
190 /// Copy the TcHook and change the values on the copied hook for easier [`Self::detach()`]
191 ///
192 /// NOTE: Once a [`TcHook`] is attached, it, and the maps it uses, will outlive the userspace
193 /// application that spawned them Make sure to detach if this is not desired
194 pub fn attach(&mut self) -> Result<Self> {
195 self.opts.prog_id = 0;
196 let err =
197 unsafe { libbpf_sys::bpf_tc_attach(&self.hook as *const _, &mut self.opts as *mut _) };
198 if err != 0 {
199 Err(Error::from(io::Error::last_os_error()))
200 } else {
201 Ok(*self)
202 }
203 }
204
205 /// Detach a filter from a [`TcHook`]
206 pub fn detach(&mut self) -> Result<()> {
207 let mut opts = self.opts;
208 opts.prog_id = 0;
209 opts.prog_fd = 0;
210 opts.flags = 0;
211
212 let err = unsafe { libbpf_sys::bpf_tc_detach(&self.hook as *const _, &opts as *const _) };
213 if err != 0 {
214 Err(Error::from_raw_os_error(-err))
215 } else {
216 self.opts.prog_id = 0;
217 Ok(())
218 }
219 }
220
221 /// Destroy attached filters
222 ///
223 /// If called on a hook with an attach_point of `TC_EGRESS`, will detach all egress hooks
224 ///
225 /// If called on a hook with an attach_point of `TC_INGRESS`, will detach all ingress hooks
226 ///
227 /// If called on a hook with an attach_point of `TC_EGRESS|TC_INGRESS`, will destroy the clsact
228 /// tc qdisc and detach all hooks
229 ///
230 /// Will error with EOPNOTSUPP if attach_point is `TC_CUSTOM`
231 ///
232 /// It is good practice to query before destroying as the tc qdisc may be used by multiple
233 /// programs
234 pub fn destroy(&mut self) -> Result<()> {
235 let err = unsafe { libbpf_sys::bpf_tc_hook_destroy(&mut self.hook as *mut _) };
236 if err != 0 {
237 Err(Error::from_raw_os_error(-err))
238 } else {
239 Ok(())
240 }
241 }
242}
243
244/// Builds [`TcHook`] instances.
245///
246/// [`TcHookBuilder`] is a way to ergonomically create multiple `TcHook`s,
247/// all with similar initial values.
248///
249/// Once a `TcHook` is created via the [`Self::hook()`] method, the `TcHook`'s values can still
250/// be adjusted before [`TcHook::attach()`] is called.
251#[derive(Debug)]
252pub struct TcHookBuilder<'fd> {
253 fd: BorrowedFd<'fd>,
254 ifindex: i32,
255 parent_maj: u32,
256 parent_min: u32,
257 replace: bool,
258 handle: u32,
259 priority: u32,
260}
261
262impl<'fd> TcHookBuilder<'fd> {
263 /// Create a new `TcHookBuilder` with fd
264 /// this fd should come from a loaded [`Program`][crate::Program]
265 pub fn new(fd: BorrowedFd<'fd>) -> Self {
266 TcHookBuilder {
267 fd,
268 ifindex: 0,
269 parent_maj: 0,
270 parent_min: 0,
271 replace: false,
272 handle: 0,
273 priority: 0,
274 }
275 }
276
277 /// Set the initial interface index to attach the hook on
278 pub fn ifindex(&mut self, ifindex: i32) -> &mut Self {
279 self.ifindex = ifindex;
280 self
281 }
282
283 /// Set the initial parent of a hook
284 pub fn parent(&mut self, maj: u32, min: u32) -> &mut Self {
285 self.parent_maj = maj;
286 self.parent_min = min;
287 self
288 }
289
290 /// Set whether created hooks should replace existing hooks
291 pub fn replace(&mut self, replace: bool) -> &mut Self {
292 self.replace = replace;
293 self
294 }
295
296 /// Set the initial handle for a hook
297 pub fn handle(&mut self, handle: u32) -> &mut Self {
298 self.handle = handle;
299 self
300 }
301
302 /// Set the initial priority for a hook
303 pub fn priority(&mut self, priority: u32) -> &mut Self {
304 self.priority = priority;
305 self
306 }
307
308 /// Create a [`TcHook`] given the values previously set
309 ///
310 /// Once a hook is created, the values can still be changed on the `TcHook`
311 /// by calling the `TcHooks` setter methods
312 pub fn hook(&self, attach_point: TcAttachPoint) -> TcHook {
313 let mut hook = TcHook::new(self.fd);
314 hook.ifindex(self.ifindex)
315 .handle(self.handle)
316 .priority(self.priority)
317 .parent(self.parent_maj, self.parent_min)
318 .replace(self.replace)
319 .attach_point(attach_point);
320
321 hook
322 }
323}