1 use std::io;
2 use std::mem::size_of;
3 use std::os::unix::io::AsRawFd;
4 use std::os::unix::io::BorrowedFd;
5 
6 use crate::Error;
7 use crate::Result;
8 
9 /// See [`libbpf_sys::bpf_tc_attach_point`].
10 pub type TcAttachPoint = libbpf_sys::bpf_tc_attach_point;
11 /// See [`libbpf_sys::BPF_TC_INGRESS`].
12 pub const TC_INGRESS: TcAttachPoint = libbpf_sys::BPF_TC_INGRESS;
13 /// See [`libbpf_sys::BPF_TC_EGRESS`].
14 pub const TC_EGRESS: TcAttachPoint = libbpf_sys::BPF_TC_EGRESS;
15 /// See [`libbpf_sys::BPF_TC_CUSTOM`].
16 pub const TC_CUSTOM: TcAttachPoint = libbpf_sys::BPF_TC_CUSTOM;
17 
18 pub type TcFlags = libbpf_sys::bpf_tc_flags;
19 /// See [`libbpf_sys::BPF_TC_F_REPLACE`].
20 pub 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)]
24 pub const TC_H_INGRESS: u32 = 0xFFFFFFF1;
25 #[allow(missing_docs)]
26 pub const TC_H_CLSACT: u32 = TC_H_INGRESS;
27 #[allow(missing_docs)]
28 pub const TC_H_MIN_INGRESS: u32 = 0xFFF2;
29 #[allow(missing_docs)]
30 pub const TC_H_MIN_EGRESS: u32 = 0xFFF3;
31 #[allow(missing_docs)]
32 pub const TC_H_MAJ_MASK: u32 = 0xFFFF0000;
33 #[allow(missing_docs)]
34 pub 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/[email protected]/).
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)]
51 pub struct TcHook {
52     hook: libbpf_sys::bpf_tc_hook,
53     opts: libbpf_sys::bpf_tc_opts,
54 }
55 
56 impl TcHook {
57     /// Create a new [`TcHook`] given the file descriptor of the loaded
58     /// `SEC("tc")` [`Program`][crate::Program].
new(fd: BorrowedFd<'_>) -> Self59     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
create(&mut self) -> Result<Self>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
ifindex(&mut self, idx: i32) -> &mut Self96     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
attach_point(&mut self, ap: TcAttachPoint) -> &mut Self108     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`
parent(&mut self, maj: u32, min: u32) -> &mut Self122     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()`]
replace(&mut self, replace: bool) -> &mut Self133     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
handle(&mut self, handle: u32) -> &mut Self144     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
get_handle(&self) -> u32151     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
priority(&mut self, priority: u32) -> &mut Self157     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
get_priority(&self) -> u32164     pub fn get_priority(&self) -> u32 {
165         self.opts.priority
166     }
167 
168     /// Query a hook to inspect the program identifier (prog_id)
query(&mut self) -> Result<u32>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
attach(&mut self) -> Result<Self>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`]
detach(&mut self) -> Result<()>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
destroy(&mut self) -> Result<()>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)]
252 pub 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 
262 impl<'fd> TcHookBuilder<'fd> {
263     /// Create a new `TcHookBuilder` with fd
264     /// this fd should come from a loaded [`Program`][crate::Program]
new(fd: BorrowedFd<'fd>) -> Self265     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
ifindex(&mut self, ifindex: i32) -> &mut Self278     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
parent(&mut self, maj: u32, min: u32) -> &mut Self284     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
replace(&mut self, replace: bool) -> &mut Self291     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
handle(&mut self, handle: u32) -> &mut Self297     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
priority(&mut self, priority: u32) -> &mut Self303     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
hook(&self, attach_point: TcAttachPoint) -> TcHook312     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 }
324