aya_friday/programs/tc.rs
1//! Network traffic control programs.
2use std::{ffi::CString, io, os::fd::AsFd as _, path::Path};
3
4use aya_obj::generated::{
5 TC_H_CLSACT, TC_H_MIN_EGRESS, TC_H_MIN_INGRESS,
6 bpf_attach_type::{self, BPF_TCX_EGRESS, BPF_TCX_INGRESS},
7 bpf_link_type,
8 bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS,
9};
10use thiserror::Error;
11
12use super::{FdLink, ProgramInfo};
13use crate::{
14 VerifierLogLevel,
15 programs::{
16 Link, LinkError, LinkOrder, ProgramData, ProgramError, ProgramType, define_link_wrapper,
17 id_as_key, impl_try_from_fdlink, impl_try_into_fdlink, load_program_without_attach_type,
18 query,
19 },
20 sys::{
21 BpfLinkCreateArgs, LinkTarget, NetlinkError, NetlinkSocket, ProgQueryTarget, SyscallError,
22 bpf_link_create, bpf_link_update, bpf_prog_get_fd_by_id, netlink_find_filter_with_name,
23 netlink_qdisc_add_clsact, netlink_qdisc_attach, netlink_qdisc_detach,
24 },
25 util::{KernelVersion, ifindex_from_ifname, tc_handler_make},
26};
27
28/// Traffic control attach type.
29#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
30pub enum TcAttachType {
31 /// Attach to ingress.
32 Ingress,
33 /// Attach to egress.
34 Egress,
35 /// Attach to custom parent.
36 Custom(u32),
37}
38
39/// A network traffic control classifier.
40///
41/// [`SchedClassifier`] programs can be used to inspect, filter or redirect
42/// network packets in both ingress and egress. They are executed as part of the
43/// linux network traffic control system. See
44/// [https://man7.org/linux/man-pages/man8/tc-bpf.8.html](https://man7.org/linux/man-pages/man8/tc-bpf.8.html).
45///
46/// # Examples
47///
48/// # Minimum kernel version
49///
50/// The minimum kernel version required to use this feature is 4.1.
51///
52/// ```no_run
53/// # #[derive(Debug, thiserror::Error)]
54/// # enum Error {
55/// # #[error(transparent)]
56/// # IO(#[from] std::io::Error),
57/// # #[error(transparent)]
58/// # Map(#[from] aya::maps::MapError),
59/// # #[error(transparent)]
60/// # Program(#[from] aya::programs::ProgramError),
61/// # #[error(transparent)]
62/// # Tc(#[from] aya::programs::tc::TcError),
63/// # #[error(transparent)]
64/// # Ebpf(#[from] aya::EbpfError)
65/// # }
66/// # let mut bpf = aya::Ebpf::load(&[])?;
67/// use aya::programs::{tc, SchedClassifier, TcAttachType};
68///
69/// // the clsact qdisc needs to be added before SchedClassifier programs can be
70/// // attached
71/// tc::qdisc_add_clsact("eth0")?;
72///
73/// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?;
74/// prog.load()?;
75/// prog.attach("eth0", TcAttachType::Ingress)?;
76///
77/// # Ok::<(), Error>(())
78/// ```
79#[derive(Debug)]
80#[doc(alias = "BPF_PROG_TYPE_SCHED_CLS")]
81pub struct SchedClassifier {
82 pub(crate) data: ProgramData<SchedClassifierLink>,
83}
84
85/// Errors from TC programs
86#[derive(Debug, Error)]
87pub enum TcError {
88 /// a netlink error occurred.
89 #[error(transparent)]
90 NetlinkError(#[from] NetlinkError),
91 /// the provided string contains a nul byte.
92 #[error(transparent)]
93 NulError(#[from] std::ffi::NulError),
94 /// an IO error occurred.
95 #[error(transparent)]
96 IoError(#[from] io::Error),
97 /// the clsact qdisc is already attached.
98 #[error("the clsact qdisc is already attached")]
99 AlreadyAttached,
100 /// tcx links can only be attached to ingress or egress, custom attachment is not supported.
101 #[error(
102 "tcx links can only be attached to ingress or egress, custom attachment: {0} is not supported"
103 )]
104 InvalidTcxAttach(u32),
105 /// operation not supported for programs loaded via tcx.
106 #[error("operation not supported for programs loaded via tcx")]
107 InvalidLinkOperation,
108}
109
110impl TcAttachType {
111 pub(crate) const fn tc_parent(self) -> u32 {
112 match self {
113 Self::Custom(parent) => parent,
114 Self::Ingress => tc_handler_make(TC_H_CLSACT, TC_H_MIN_INGRESS),
115 Self::Egress => tc_handler_make(TC_H_CLSACT, TC_H_MIN_EGRESS),
116 }
117 }
118
119 pub(crate) const fn tcx_attach_type(self) -> Result<bpf_attach_type, TcError> {
120 match self {
121 Self::Ingress => Ok(BPF_TCX_INGRESS),
122 Self::Egress => Ok(BPF_TCX_EGRESS),
123 Self::Custom(tcx_attach_type) => Err(TcError::InvalidTcxAttach(tcx_attach_type)),
124 }
125 }
126}
127
128/// Options for a [`SchedClassifier`] attach operation.
129///
130/// The options vary based on what is supported by the current kernel. Kernels
131/// older than 6.6.0 must utilize netlink for attachments, while newer kernels
132/// can utilize the modern TCX eBPF link type which supports the kernel's
133/// multi-prog API.
134#[derive(Debug)]
135pub enum TcAttachOptions {
136 /// Netlink attach options.
137 Netlink(NlOptions),
138 /// Tcx attach options.
139 TcxOrder(LinkOrder),
140}
141
142/// A TC filter handle in `major:minor` form.
143///
144/// Matches the `M:N` syntax accepted by `tc(8)`. Use [`TcHandle::AUTO_ASSIGN`]
145/// to ask the kernel to allocate one.
146#[derive(Debug, Clone, Copy, Default, Hash, Eq, PartialEq)]
147#[doc(alias = "tcm_handle")]
148pub struct TcHandle {
149 /// Upper 16 bits when encoded as a u32.
150 major: u16,
151 /// Lower 16 bits when encoded as a u32.
152 minor: u16,
153}
154
155impl TcHandle {
156 /// Sentinel that asks the kernel to allocate a handle at attach time.
157 ///
158 /// Equal to [`Default::default`]. The allocated value is exposed by
159 /// [`SchedClassifierLink::handle`] after the program is attached.
160 pub const AUTO_ASSIGN: Self = Self { major: 0, minor: 0 };
161
162 /// Const equivalent of `Self { major, minor }`.
163 pub const fn new(major: u16, minor: u16) -> Self {
164 Self { major, minor }
165 }
166}
167
168impl From<TcHandle> for u32 {
169 fn from(TcHandle { major, minor }: TcHandle) -> Self {
170 (Self::from(major) << 16) | Self::from(minor)
171 }
172}
173
174impl From<u32> for TcHandle {
175 fn from(value: u32) -> Self {
176 Self {
177 major: (value >> 16) as u16,
178 minor: value as u16,
179 }
180 }
181}
182
183/// Options for [`SchedClassifier`] attach via netlink.
184#[derive(Debug, Default, Hash, Eq, PartialEq)]
185pub struct NlOptions {
186 /// Priority assigned to tc program with lower number = higher priority.
187 /// If set to default (0), the system chooses the next highest priority or 49152 if no filters exist yet
188 pub priority: u16,
189 /// Handle used to uniquely identify a program at a given priority level.
190 ///
191 /// Defaults to [`TcHandle::AUTO_ASSIGN`], which lets the kernel pick one.
192 pub handle: TcHandle,
193 /// `classid` bound to this filter (also known as `flowid` in `tc(8)`).
194 ///
195 /// In direct-action mode the major 16 bits of the resulting class id come
196 /// from this attribute and the minor 16 bits come from the program at run
197 /// time via `__sk_buff::tc_classid`. This split requires Linux 4.6 (commit
198 /// [`3a461da1d03e`]).
199 ///
200 /// When [`None`], no attribute is sent and the filter is not bound to a
201 /// class.
202 ///
203 /// [`3a461da1d03e`]: https://github.com/torvalds/linux/commit/3a461da1d03e7a857edfa6a002040d07e118c639
204 #[doc(alias = "TCA_BPF_CLASSID")]
205 pub classid: Option<TcHandle>,
206}
207
208impl SchedClassifier {
209 /// The type of the program according to the kernel.
210 pub const PROGRAM_TYPE: ProgramType = ProgramType::SchedClassifier;
211
212 /// Loads the program inside the kernel.
213 pub fn load(&mut self) -> Result<(), ProgramError> {
214 let Self { data } = self;
215 load_program_without_attach_type(BPF_PROG_TYPE_SCHED_CLS, data)
216 }
217
218 /// Attaches the program to the given `interface`.
219 ///
220 /// On kernels >= 6.6.0, it will attempt to use the TCX interface and attach as
221 /// the last TCX program. On older kernels, it will fallback to using the
222 /// legacy netlink interface.
223 ///
224 /// For finer grained control over link ordering use [`SchedClassifier::attach_with_options`].
225 ///
226 /// The returned value can be used to detach, see [`SchedClassifier::detach`].
227 ///
228 /// # Errors
229 ///
230 /// When attaching fails, [`ProgramError::SyscallError`] is returned for
231 /// kernels `>= 6.6.0`, and [`TcError::NetlinkError`] is returned for
232 /// older kernels. A common cause of netlink attachment failure is not having added
233 /// the `clsact` qdisc to the given interface, seeĀ [`qdisc_add_clsact`]
234 ///
235 pub fn attach(
236 &mut self,
237 interface: &str,
238 attach_type: TcAttachType,
239 ) -> Result<SchedClassifierLinkId, ProgramError> {
240 if !matches!(attach_type, TcAttachType::Custom(_)) && KernelVersion::at_least(6, 6, 0) {
241 self.attach_with_options(
242 interface,
243 attach_type,
244 TcAttachOptions::TcxOrder(LinkOrder::default()),
245 )
246 } else {
247 self.attach_with_options(
248 interface,
249 attach_type,
250 TcAttachOptions::Netlink(NlOptions::default()),
251 )
252 }
253 }
254
255 /// Attaches the program to the given `interface` with options defined in [`TcAttachOptions`].
256 ///
257 /// The returned value can be used to detach, see [`SchedClassifier::detach`].
258 ///
259 /// # Link Pinning (TCX mode, kernel >= 6.6)
260 ///
261 /// Links can be pinned to bpffs for atomic replacement across process restarts.
262 ///
263 /// ```no_run
264 /// # use std::{io, path::Path};
265 ///
266 /// # use aya::{
267 /// # programs::{
268 /// # LinkOrder, SchedClassifier, TcAttachType,
269 /// # links::{FdLink, LinkError, PinnedLink},
270 /// # tc::TcAttachOptions,
271 /// # },
272 /// # sys::SyscallError,
273 /// # };
274 ///
275 /// # let mut bpf = aya::Ebpf::load(&[])?;
276 /// # let prog: &mut SchedClassifier = bpf.program_mut("prog").unwrap().try_into()?;
277 /// # prog.load()?;
278 /// let pin_path = "/sys/fs/bpf/my_link";
279 ///
280 /// let link_id = match PinnedLink::from_pin(pin_path) {
281 /// Ok(old) => {
282 /// let link = FdLink::from(old).try_into()?;
283 /// prog.attach_to_link(link)?
284 /// }
285 /// Err(LinkError::SyscallError(SyscallError { io_error, .. }))
286 /// if io_error.kind() == io::ErrorKind::NotFound =>
287 /// {
288 /// prog.attach_with_options(
289 /// "eth0",
290 /// TcAttachType::Ingress,
291 /// TcAttachOptions::TcxOrder(LinkOrder::default()),
292 /// )?
293 /// }
294 /// Err(e) => return Err(e.into()),
295 /// };
296 ///
297 /// let link = prog.take_link(link_id)?;
298 /// let fd_link: FdLink = link.try_into()?;
299 /// fd_link.pin(pin_path)?;
300 /// # Ok::<(), Box<dyn std::error::Error>>(())
301 /// ```
302 ///
303 /// # Errors
304 ///
305 /// [`TcError::NetlinkError`] is returned if attaching fails. A common cause
306 /// of failure is not having added the `clsact` qdisc to the given
307 /// interface, see [`qdisc_add_clsact`].
308 pub fn attach_with_options(
309 &mut self,
310 interface: &str,
311 attach_type: TcAttachType,
312 options: TcAttachOptions,
313 ) -> Result<SchedClassifierLinkId, ProgramError> {
314 let if_index = ifindex_from_ifname(interface).map_err(TcError::IoError)?;
315 self.do_attach(if_index, attach_type, options, true)
316 }
317
318 /// Atomically replaces the program referenced by the provided link.
319 ///
320 /// Ownership of the link will transfer to this program.
321 pub fn attach_to_link(
322 &mut self,
323 link: SchedClassifierLink,
324 ) -> Result<SchedClassifierLinkId, ProgramError> {
325 let prog_fd = self.fd()?;
326 let prog_fd = prog_fd.as_fd();
327 match link.into_inner() {
328 TcLinkInner::Fd(link) => {
329 let fd = link.fd;
330 let link_fd = fd.as_fd();
331
332 bpf_link_update(link_fd.as_fd(), prog_fd, None, 0).map_err(|io_error| {
333 SyscallError {
334 call: "bpf_link_update",
335 io_error,
336 }
337 })?;
338
339 self.data
340 .links
341 .insert(SchedClassifierLink::new(TcLinkInner::Fd(FdLink::new(fd))))
342 }
343 TcLinkInner::NlLink(NlLink {
344 if_index,
345 attach_type,
346 priority,
347 handle,
348 classid,
349 }) => self.do_attach(
350 if_index,
351 attach_type,
352 TcAttachOptions::Netlink(NlOptions {
353 priority,
354 handle,
355 classid,
356 }),
357 false,
358 ),
359 }
360 }
361
362 fn do_attach(
363 &mut self,
364 if_index: u32,
365 attach_type: TcAttachType,
366 options: TcAttachOptions,
367 create: bool,
368 ) -> Result<SchedClassifierLinkId, ProgramError> {
369 let prog_fd = self.fd()?;
370 let prog_fd = prog_fd.as_fd();
371
372 match options {
373 TcAttachOptions::Netlink(options) => {
374 let name = self.data.name.as_deref().unwrap_or_default();
375 // TODO: avoid this unwrap by adding a new error variant.
376 let name = CString::new(name).unwrap();
377 let (priority, handle) = unsafe {
378 netlink_qdisc_attach(
379 if_index as i32,
380 &attach_type,
381 prog_fd,
382 &name,
383 options.priority,
384 options.handle,
385 options.classid,
386 create,
387 )
388 }
389 .map_err(TcError::NetlinkError)?;
390
391 self.data
392 .links
393 .insert(SchedClassifierLink::new(TcLinkInner::NlLink(NlLink {
394 if_index,
395 attach_type,
396 priority,
397 handle,
398 classid: options.classid,
399 })))
400 }
401 TcAttachOptions::TcxOrder(options) => {
402 let link_fd = bpf_link_create(
403 prog_fd,
404 LinkTarget::IfIndex(if_index),
405 attach_type.tcx_attach_type()?,
406 options.flags.bits(),
407 Some(BpfLinkCreateArgs::Tcx(&options.link_ref)),
408 )
409 .map_err(|io_error| SyscallError {
410 call: "bpf_mprog_attach",
411 io_error,
412 })?;
413
414 self.data
415 .links
416 .insert(SchedClassifierLink::new(TcLinkInner::Fd(FdLink::new(
417 link_fd,
418 ))))
419 }
420 }
421 }
422
423 /// Creates a program from a pinned entry on a bpffs.
424 ///
425 /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
426 ///
427 /// On drop, any managed links are detached and the program is unloaded. This will not result in
428 /// the program being unloaded from the kernel if it is still pinned.
429 pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
430 let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
431 Ok(Self { data })
432 }
433
434 /// Queries a given interface for attached TCX programs.
435 ///
436 /// # Example
437 ///
438 /// ```no_run
439 /// # use aya::programs::tc::{TcAttachType, SchedClassifier};
440 /// # #[derive(Debug, thiserror::Error)]
441 /// # enum Error {
442 /// # #[error(transparent)]
443 /// # Program(#[from] aya::programs::ProgramError),
444 /// # }
445 /// let (revision, programs) = SchedClassifier::query_tcx("eth0", TcAttachType::Ingress)?;
446 /// # Ok::<(), Error>(())
447 /// ```
448 pub fn query_tcx(
449 interface: &str,
450 attach_type: TcAttachType,
451 ) -> Result<(u64, Vec<ProgramInfo>), ProgramError> {
452 let if_index = ifindex_from_ifname(interface).map_err(TcError::IoError)?;
453
454 let (revision, prog_ids) = query(
455 ProgQueryTarget::IfIndex(if_index),
456 attach_type.tcx_attach_type()?,
457 0,
458 &mut None,
459 )?;
460
461 let prog_infos = prog_ids
462 .into_iter()
463 .map(|prog_id| {
464 let prog_fd = bpf_prog_get_fd_by_id(prog_id)?;
465 let prog_info = ProgramInfo::new_from_fd(prog_fd.as_fd())?;
466 Ok::<ProgramInfo, ProgramError>(prog_info)
467 })
468 .collect::<Result<_, _>>()?;
469
470 Ok((revision, prog_infos))
471 }
472}
473
474#[derive(Debug, Hash, Eq, PartialEq)]
475pub(crate) struct NlLinkId(u32, TcAttachType, u16, TcHandle);
476
477#[derive(Debug)]
478pub(crate) struct NlLink {
479 if_index: u32,
480 attach_type: TcAttachType,
481 priority: u16,
482 handle: TcHandle,
483 classid: Option<TcHandle>,
484}
485
486impl Link for NlLink {
487 type Id = NlLinkId;
488
489 fn id(&self) -> Self::Id {
490 NlLinkId(self.if_index, self.attach_type, self.priority, self.handle)
491 }
492
493 fn detach(self) -> Result<(), ProgramError> {
494 unsafe {
495 netlink_qdisc_detach(
496 self.if_index as i32,
497 self.attach_type,
498 self.priority,
499 self.handle,
500 )
501 }
502 .map_err(ProgramError::NetlinkError)?;
503 Ok(())
504 }
505}
506
507id_as_key!(NlLink, NlLinkId);
508
509#[derive(Debug, Hash, Eq, PartialEq)]
510pub(crate) enum TcLinkIdInner {
511 FdLinkId(<FdLink as Link>::Id),
512 NlLinkId(<NlLink as Link>::Id),
513}
514
515#[derive(Debug)]
516pub(crate) enum TcLinkInner {
517 Fd(FdLink),
518 NlLink(NlLink),
519}
520
521impl Link for TcLinkInner {
522 type Id = TcLinkIdInner;
523
524 fn id(&self) -> Self::Id {
525 match self {
526 Self::Fd(link) => TcLinkIdInner::FdLinkId(link.id()),
527 Self::NlLink(link) => TcLinkIdInner::NlLinkId(link.id()),
528 }
529 }
530
531 fn detach(self) -> Result<(), ProgramError> {
532 match self {
533 Self::Fd(link) => link.detach(),
534 Self::NlLink(link) => link.detach(),
535 }
536 }
537}
538
539id_as_key!(TcLinkInner, TcLinkIdInner);
540
541impl<'a> TryFrom<&'a SchedClassifierLink> for &'a FdLink {
542 type Error = LinkError;
543
544 fn try_from(value: &'a SchedClassifierLink) -> Result<Self, Self::Error> {
545 if let TcLinkInner::Fd(fd) = value.inner() {
546 Ok(fd)
547 } else {
548 Err(LinkError::InvalidLink)
549 }
550 }
551}
552
553impl_try_into_fdlink!(SchedClassifierLink, TcLinkInner);
554impl_try_from_fdlink!(
555 SchedClassifierLink,
556 TcLinkInner,
557 bpf_link_type::BPF_LINK_TYPE_TCX
558);
559
560define_link_wrapper!(
561 SchedClassifierLink,
562 SchedClassifierLinkId,
563 TcLinkInner,
564 TcLinkIdInner,
565 SchedClassifier,
566);
567
568impl SchedClassifierLink {
569 /// Constructs a [`SchedClassifierLink`] where the `if_name`, `attach_type`,
570 /// `priority` and `handle` are already known. This may have been found from a link created by
571 /// [`SchedClassifier::attach`], the output of the `tc filter` command or from the output of
572 /// another BPF loader.
573 ///
574 /// Note: If you create a link for a program that you do not own, detaching it may have
575 /// unintended consequences.
576 ///
577 /// # Errors
578 /// Returns [`io::Error`] if `if_name` is invalid. If the other parameters are invalid this call
579 /// will succeed, but calling [`SchedClassifierLink::detach`] will return [`TcError::NetlinkError`].
580 ///
581 /// # Examples
582 /// ```no_run
583 /// # use aya::programs::tc::{SchedClassifierLink, TcHandle};
584 /// # use aya::programs::TcAttachType;
585 /// # #[derive(Debug, thiserror::Error)]
586 /// # enum Error {
587 /// # #[error(transparent)]
588 /// # IO(#[from] std::io::Error),
589 /// # }
590 /// # fn read_persisted_link_details() -> (&'static str, TcAttachType, u16, TcHandle, Option<TcHandle>) {
591 /// # ("eth0", TcAttachType::Ingress, 50, TcHandle::new(0, 1), None)
592 /// # }
593 /// // Get the link parameters from some external source. Where and how the parameters are
594 /// // persisted is up to your application.
595 /// let (if_name, attach_type, priority, handle, classid) = read_persisted_link_details();
596 /// let new_tc_link =
597 /// SchedClassifierLink::attached(if_name, attach_type, priority, handle, classid)?;
598 ///
599 /// # Ok::<(), Error>(())
600 /// ```
601 pub fn attached(
602 if_name: &str,
603 attach_type: TcAttachType,
604 priority: u16,
605 handle: TcHandle,
606 classid: Option<TcHandle>,
607 ) -> Result<Self, io::Error> {
608 let if_index = ifindex_from_ifname(if_name)?;
609 Ok(Self(Some(TcLinkInner::NlLink(NlLink {
610 if_index,
611 attach_type,
612 priority,
613 handle,
614 classid,
615 }))))
616 }
617
618 /// Returns the attach type.
619 pub fn attach_type(&self) -> Result<TcAttachType, ProgramError> {
620 if let TcLinkInner::NlLink(n) = self.inner() {
621 Ok(n.attach_type)
622 } else {
623 Err(TcError::InvalidLinkOperation.into())
624 }
625 }
626
627 /// Returns the allocated priority. If none was provided at attach time, this was allocated for you.
628 pub fn priority(&self) -> Result<u16, ProgramError> {
629 if let TcLinkInner::NlLink(n) = self.inner() {
630 Ok(n.priority)
631 } else {
632 Err(TcError::InvalidLinkOperation.into())
633 }
634 }
635
636 /// Returns the assigned handle. If none was provided at attach time, this was allocated for you.
637 pub fn handle(&self) -> Result<TcHandle, ProgramError> {
638 if let TcLinkInner::NlLink(n) = self.inner() {
639 Ok(n.handle)
640 } else {
641 Err(TcError::InvalidLinkOperation.into())
642 }
643 }
644
645 /// Returns the `classid` bound to this filter, or [`None`] if the filter
646 /// is not bound to a class. See [`NlOptions::classid`].
647 pub fn classid(&self) -> Result<Option<TcHandle>, ProgramError> {
648 if let TcLinkInner::NlLink(n) = self.inner() {
649 Ok(n.classid)
650 } else {
651 Err(TcError::InvalidLinkOperation.into())
652 }
653 }
654}
655
656/// Add the `clasct` qdisc to the given interface.
657///
658/// The `clsact` qdisc must be added to an interface before [`SchedClassifier`]
659/// programs can be attached.
660pub fn qdisc_add_clsact(if_name: &str) -> Result<(), TcError> {
661 let if_index = ifindex_from_ifname(if_name)?;
662 unsafe { netlink_qdisc_add_clsact(if_index as i32).map_err(TcError::NetlinkError) }
663}
664
665/// Detaches the programs with the given name.
666///
667/// # Errors
668///
669/// Returns [`io::ErrorKind::NotFound`] to indicate that no programs with the
670/// given name were found, so nothing was detached. Other error kinds indicate
671/// an actual failure while detaching a program.
672pub fn qdisc_detach_program(
673 if_name: &str,
674 attach_type: TcAttachType,
675 name: &str,
676) -> Result<(), TcError> {
677 let cstr = CString::new(name).map_err(TcError::NulError)?;
678 let if_index = ifindex_from_ifname(if_name)? as i32;
679
680 let sock = NetlinkSocket::open().map_err(NetlinkError::from)?;
681 let filter_info = netlink_find_filter_with_name(&sock, if_index, attach_type, &cstr)?;
682 // Check for errors before detaching any programs.
683 let filter_info: Vec<_> = filter_info.collect::<Result<_, _>>()?;
684 if filter_info.is_empty() {
685 return Err(TcError::IoError(io::Error::new(
686 io::ErrorKind::NotFound,
687 name.to_owned(),
688 )));
689 }
690
691 for (prio, handle) in filter_info {
692 unsafe { netlink_qdisc_detach(if_index, attach_type, prio, handle)? }
693 }
694
695 Ok(())
696}