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