Skip to main content

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}