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