1 //! Query the host about BPF
2 //!
3 //! For example, to list the name of every bpf program running on the system:
4 //! ```
5 //! use libbpf_rs::query::ProgInfoIter;
6 //!
7 //! let mut iter = ProgInfoIter::default();
8 //! for prog in iter {
9 //!     println!("{}", prog.name.to_string_lossy());
10 //! }
11 //! ```
12 
13 use std::ffi::c_void;
14 use std::ffi::CString;
15 use std::io;
16 use std::mem::size_of_val;
17 use std::os::fd::AsFd;
18 use std::os::fd::AsRawFd;
19 use std::os::fd::BorrowedFd;
20 use std::os::fd::FromRawFd;
21 use std::os::fd::OwnedFd;
22 use std::os::raw::c_char;
23 use std::ptr;
24 use std::time::Duration;
25 
26 use crate::util;
27 use crate::MapType;
28 use crate::ProgramAttachType;
29 use crate::ProgramType;
30 use crate::Result;
31 
32 macro_rules! gen_info_impl {
33     // This magic here allows us to embed doc comments into macro expansions
34     ($(#[$attr:meta])*
35      $name:ident, $info_ty:ty, $uapi_info_ty:ty, $next_id:expr, $fd_by_id:expr) => {
36         $(#[$attr])*
37         #[derive(Default, Debug)]
38         pub struct $name {
39             cur_id: u32,
40         }
41 
42         impl $name {
43             // Returns Some(next_valid_fd), None on none left
44             fn next_valid_fd(&mut self) -> Option<OwnedFd> {
45                 loop {
46                     if unsafe { $next_id(self.cur_id, &mut self.cur_id) } != 0 {
47                         return None;
48                     }
49 
50                     let fd = unsafe { $fd_by_id(self.cur_id) };
51                     if fd < 0 {
52                         let err = io::Error::last_os_error();
53                         if err.kind() == io::ErrorKind::NotFound {
54                             continue;
55                         }
56 
57                         return None;
58                     }
59 
60                     return Some(unsafe { OwnedFd::from_raw_fd(fd)});
61                 }
62             }
63         }
64 
65         impl Iterator for $name {
66             type Item = $info_ty;
67 
68             fn next(&mut self) -> Option<Self::Item> {
69                 let fd = self.next_valid_fd()?;
70 
71                 // We need to use std::mem::zeroed() instead of just using
72                 // ::default() because padding bytes need to be zero as well.
73                 // Old kernels which know about fewer fields than we do will
74                 // check to make sure every byte past what they know is zero
75                 // and will return E2BIG otherwise.
76                 let mut item: $uapi_info_ty = unsafe { std::mem::zeroed() };
77                 let item_ptr: *mut $uapi_info_ty = &mut item;
78                 let mut len = size_of_val(&item) as u32;
79 
80                 let ret = unsafe { libbpf_sys::bpf_obj_get_info_by_fd(fd.as_raw_fd(), item_ptr as *mut c_void, &mut len) };
81                 let parsed_uapi = if ret != 0 {
82                     None
83                 } else {
84                     <$info_ty>::from_uapi(fd.as_fd(), item)
85                 };
86 
87                 parsed_uapi
88             }
89         }
90     };
91 }
92 
93 /// BTF Line information
94 #[derive(Clone, Debug)]
95 pub struct LineInfo {
96     /// Offset of instruction in vector
97     pub insn_off: u32,
98     /// File name offset
99     pub file_name_off: u32,
100     /// Line offset in debug info
101     pub line_off: u32,
102     /// Line number
103     pub line_num: u32,
104     /// Line column number
105     pub line_col: u32,
106 }
107 
108 impl From<&libbpf_sys::bpf_line_info> for LineInfo {
from(item: &libbpf_sys::bpf_line_info) -> Self109     fn from(item: &libbpf_sys::bpf_line_info) -> Self {
110         LineInfo {
111             insn_off: item.insn_off,
112             file_name_off: item.file_name_off,
113             line_off: item.line_off,
114             line_num: item.line_col >> 10,
115             line_col: item.line_col & 0x3ff,
116         }
117     }
118 }
119 
120 /// Bpf identifier tag
121 #[derive(Debug, Clone, Default)]
122 #[repr(C)]
123 pub struct Tag(pub [u8; 8]);
124 
125 /// Information about a BPF program
126 #[derive(Debug, Clone)]
127 // TODO: Document members.
128 #[allow(missing_docs)]
129 pub struct ProgramInfo {
130     pub name: CString,
131     pub ty: ProgramType,
132     pub tag: Tag,
133     pub id: u32,
134     pub jited_prog_insns: Vec<u8>,
135     pub xlated_prog_insns: Vec<u8>,
136     /// Duration since system boot
137     pub load_time: Duration,
138     pub created_by_uid: u32,
139     pub map_ids: Vec<u32>,
140     pub ifindex: u32,
141     pub gpl_compatible: bool,
142     pub netns_dev: u64,
143     pub netns_ino: u64,
144     pub jited_ksyms: Vec<*const c_void>,
145     pub jited_func_lens: Vec<u32>,
146     pub btf_id: u32,
147     pub func_info_rec_size: u32,
148     pub func_info: Vec<libbpf_sys::bpf_func_info>,
149     pub line_info: Vec<LineInfo>,
150     pub jited_line_info: Vec<*const c_void>,
151     pub line_info_rec_size: u32,
152     pub jited_line_info_rec_size: u32,
153     pub prog_tags: Vec<Tag>,
154     pub run_time_ns: u64,
155     pub run_cnt: u64,
156     /// Skipped BPF executions due to recursion or concurrent execution prevention.
157     pub recursion_misses: u64,
158 }
159 
160 /// An iterator for the information of loaded bpf programs
161 #[derive(Default, Debug)]
162 pub struct ProgInfoIter {
163     cur_id: u32,
164     opts: ProgInfoQueryOptions,
165 }
166 
167 /// Options to query the program info currently loaded
168 #[derive(Clone, Default, Debug)]
169 pub struct ProgInfoQueryOptions {
170     /// Include the vector of bpf instructions in the result
171     include_xlated_prog_insns: bool,
172     /// Include the vector of jited instructions in the result
173     include_jited_prog_insns: bool,
174     /// Include the ids of maps associated with the program
175     include_map_ids: bool,
176     /// Include source line information corresponding to xlated code
177     include_line_info: bool,
178     /// Include function type information corresponding to xlated code
179     include_func_info: bool,
180     /// Include source line information corresponding to jited code
181     include_jited_line_info: bool,
182     /// Include function type information corresponding to jited code
183     include_jited_func_lens: bool,
184     /// Include program tags
185     include_prog_tags: bool,
186     /// Include the jited kernel symbols
187     include_jited_ksyms: bool,
188 }
189 
190 impl ProgInfoIter {
191     /// Generate an iter from more specific query options
with_query_opts(opts: ProgInfoQueryOptions) -> Self192     pub fn with_query_opts(opts: ProgInfoQueryOptions) -> Self {
193         Self {
194             opts,
195             ..Self::default()
196         }
197     }
198 }
199 
200 impl ProgInfoQueryOptions {
201     /// Include the vector of jited bpf instructions in the result
include_xlated_prog_insns(mut self, v: bool) -> Self202     pub fn include_xlated_prog_insns(mut self, v: bool) -> Self {
203         self.include_xlated_prog_insns = v;
204         self
205     }
206 
207     /// Include the vector of jited instructions in the result
include_jited_prog_insns(mut self, v: bool) -> Self208     pub fn include_jited_prog_insns(mut self, v: bool) -> Self {
209         self.include_jited_prog_insns = v;
210         self
211     }
212 
213     /// Include the ids of maps associated with the program
include_map_ids(mut self, v: bool) -> Self214     pub fn include_map_ids(mut self, v: bool) -> Self {
215         self.include_map_ids = v;
216         self
217     }
218 
219     /// Include source line information corresponding to xlated code
include_line_info(mut self, v: bool) -> Self220     pub fn include_line_info(mut self, v: bool) -> Self {
221         self.include_line_info = v;
222         self
223     }
224 
225     /// Include function type information corresponding to xlated code
include_func_info(mut self, v: bool) -> Self226     pub fn include_func_info(mut self, v: bool) -> Self {
227         self.include_func_info = v;
228         self
229     }
230 
231     /// Include source line information corresponding to jited code
include_jited_line_info(mut self, v: bool) -> Self232     pub fn include_jited_line_info(mut self, v: bool) -> Self {
233         self.include_jited_line_info = v;
234         self
235     }
236 
237     /// Include function type information corresponding to jited code
include_jited_func_lens(mut self, v: bool) -> Self238     pub fn include_jited_func_lens(mut self, v: bool) -> Self {
239         self.include_jited_func_lens = v;
240         self
241     }
242 
243     /// Include program tags
include_prog_tags(mut self, v: bool) -> Self244     pub fn include_prog_tags(mut self, v: bool) -> Self {
245         self.include_prog_tags = v;
246         self
247     }
248 
249     /// Include the jited kernel symbols
include_jited_ksyms(mut self, v: bool) -> Self250     pub fn include_jited_ksyms(mut self, v: bool) -> Self {
251         self.include_jited_ksyms = v;
252         self
253     }
254 
255     /// Include everything there is in the query results
include_all(self) -> Self256     pub fn include_all(self) -> Self {
257         Self {
258             include_xlated_prog_insns: true,
259             include_jited_prog_insns: true,
260             include_map_ids: true,
261             include_line_info: true,
262             include_func_info: true,
263             include_jited_line_info: true,
264             include_jited_func_lens: true,
265             include_prog_tags: true,
266             include_jited_ksyms: true,
267         }
268     }
269 }
270 
271 impl ProgramInfo {
load_from_fd(fd: BorrowedFd<'_>, opts: &ProgInfoQueryOptions) -> Result<Self>272     fn load_from_fd(fd: BorrowedFd<'_>, opts: &ProgInfoQueryOptions) -> Result<Self> {
273         let mut item = libbpf_sys::bpf_prog_info::default();
274 
275         let mut xlated_prog_insns: Vec<u8> = Vec::new();
276         let mut jited_prog_insns: Vec<u8> = Vec::new();
277         let mut map_ids: Vec<u32> = Vec::new();
278         let mut jited_line_info: Vec<*const c_void> = Vec::new();
279         let mut line_info: Vec<libbpf_sys::bpf_line_info> = Vec::new();
280         let mut func_info: Vec<libbpf_sys::bpf_func_info> = Vec::new();
281         let mut jited_func_lens: Vec<u32> = Vec::new();
282         let mut prog_tags: Vec<Tag> = Vec::new();
283         let mut jited_ksyms: Vec<*const c_void> = Vec::new();
284 
285         let item_ptr: *mut libbpf_sys::bpf_prog_info = &mut item;
286         let mut len = size_of_val(&item) as u32;
287 
288         let ret = unsafe {
289             libbpf_sys::bpf_obj_get_info_by_fd(fd.as_raw_fd(), item_ptr as *mut c_void, &mut len)
290         };
291         util::parse_ret(ret)?;
292 
293         // SANITY: `libbpf` should guarantee NUL termination.
294         let name = util::c_char_slice_to_cstr(&item.name).unwrap();
295         let ty = ProgramType::from(item.type_);
296 
297         if opts.include_xlated_prog_insns {
298             xlated_prog_insns.resize(item.xlated_prog_len as usize, 0u8);
299             item.xlated_prog_insns = xlated_prog_insns.as_mut_ptr() as *mut c_void as u64;
300         } else {
301             item.xlated_prog_len = 0;
302         }
303 
304         if opts.include_jited_prog_insns {
305             jited_prog_insns.resize(item.jited_prog_len as usize, 0u8);
306             item.jited_prog_insns = jited_prog_insns.as_mut_ptr() as *mut c_void as u64;
307         } else {
308             item.jited_prog_len = 0;
309         }
310 
311         if opts.include_map_ids {
312             map_ids.resize(item.nr_map_ids as usize, 0u32);
313             item.map_ids = map_ids.as_mut_ptr() as *mut c_void as u64;
314         } else {
315             item.nr_map_ids = 0;
316         }
317 
318         if opts.include_line_info {
319             line_info.resize(
320                 item.nr_line_info as usize,
321                 libbpf_sys::bpf_line_info::default(),
322             );
323             item.line_info = line_info.as_mut_ptr() as *mut c_void as u64;
324         } else {
325             item.nr_line_info = 0;
326         }
327 
328         if opts.include_func_info {
329             func_info.resize(
330                 item.nr_func_info as usize,
331                 libbpf_sys::bpf_func_info::default(),
332             );
333             item.func_info = func_info.as_mut_ptr() as *mut c_void as u64;
334         } else {
335             item.nr_func_info = 0;
336         }
337 
338         if opts.include_jited_line_info {
339             jited_line_info.resize(item.nr_jited_line_info as usize, ptr::null());
340             item.jited_line_info = jited_line_info.as_mut_ptr() as *mut c_void as u64;
341         } else {
342             item.nr_jited_line_info = 0;
343         }
344 
345         if opts.include_jited_func_lens {
346             jited_func_lens.resize(item.nr_jited_func_lens as usize, 0);
347             item.jited_func_lens = jited_func_lens.as_mut_ptr() as *mut c_void as u64;
348         } else {
349             item.nr_jited_func_lens = 0;
350         }
351 
352         if opts.include_prog_tags {
353             prog_tags.resize(item.nr_prog_tags as usize, Tag::default());
354             item.prog_tags = prog_tags.as_mut_ptr() as *mut c_void as u64;
355         } else {
356             item.nr_prog_tags = 0;
357         }
358 
359         if opts.include_jited_ksyms {
360             jited_ksyms.resize(item.nr_jited_ksyms as usize, ptr::null());
361             item.jited_ksyms = jited_ksyms.as_mut_ptr() as *mut c_void as u64;
362         } else {
363             item.nr_jited_ksyms = 0;
364         }
365 
366         let ret = unsafe {
367             libbpf_sys::bpf_obj_get_info_by_fd(fd.as_raw_fd(), item_ptr as *mut c_void, &mut len)
368         };
369         util::parse_ret(ret)?;
370 
371         return Ok(ProgramInfo {
372             name: name.to_owned(),
373             ty,
374             tag: Tag(item.tag),
375             id: item.id,
376             jited_prog_insns,
377             xlated_prog_insns,
378             load_time: Duration::from_nanos(item.load_time),
379             created_by_uid: item.created_by_uid,
380             map_ids,
381             ifindex: item.ifindex,
382             gpl_compatible: item._bitfield_1.get_bit(0),
383             netns_dev: item.netns_dev,
384             netns_ino: item.netns_ino,
385             jited_ksyms,
386             jited_func_lens,
387             btf_id: item.btf_id,
388             func_info_rec_size: item.func_info_rec_size,
389             func_info,
390             line_info: line_info.iter().map(|li| li.into()).collect(),
391             jited_line_info,
392             line_info_rec_size: item.line_info_rec_size,
393             jited_line_info_rec_size: item.jited_line_info_rec_size,
394             prog_tags,
395             run_time_ns: item.run_time_ns,
396             run_cnt: item.run_cnt,
397             recursion_misses: item.recursion_misses,
398         });
399     }
400 }
401 
402 impl ProgInfoIter {
next_valid_fd(&mut self) -> Option<OwnedFd>403     fn next_valid_fd(&mut self) -> Option<OwnedFd> {
404         loop {
405             if unsafe { libbpf_sys::bpf_prog_get_next_id(self.cur_id, &mut self.cur_id) } != 0 {
406                 return None;
407             }
408 
409             let fd = unsafe { libbpf_sys::bpf_prog_get_fd_by_id(self.cur_id) };
410             if fd < 0 {
411                 let err = io::Error::last_os_error();
412                 if err.kind() == io::ErrorKind::NotFound {
413                     continue;
414                 }
415                 return None;
416             }
417 
418             return Some(unsafe { OwnedFd::from_raw_fd(fd) });
419         }
420     }
421 }
422 
423 impl Iterator for ProgInfoIter {
424     type Item = ProgramInfo;
425 
next(&mut self) -> Option<Self::Item>426     fn next(&mut self) -> Option<Self::Item> {
427         let fd = self.next_valid_fd()?;
428 
429         let prog = ProgramInfo::load_from_fd(fd.as_fd(), &self.opts);
430 
431         match prog {
432             Ok(p) => Some(p),
433             // TODO: We should consider bubbling up errors properly.
434             Err(_err) => None,
435         }
436     }
437 }
438 
439 /// Information about a BPF map
440 #[derive(Debug, Clone)]
441 // TODO: Document members.
442 #[allow(missing_docs)]
443 pub struct MapInfo {
444     pub name: CString,
445     pub ty: MapType,
446     pub id: u32,
447     pub key_size: u32,
448     pub value_size: u32,
449     pub max_entries: u32,
450     pub map_flags: u32,
451     pub ifindex: u32,
452     pub btf_vmlinux_value_type_id: u32,
453     pub netns_dev: u64,
454     pub netns_ino: u64,
455     pub btf_id: u32,
456     pub btf_key_type_id: u32,
457     pub btf_value_type_id: u32,
458 }
459 
460 impl MapInfo {
from_uapi(_fd: BorrowedFd<'_>, s: libbpf_sys::bpf_map_info) -> Option<Self>461     fn from_uapi(_fd: BorrowedFd<'_>, s: libbpf_sys::bpf_map_info) -> Option<Self> {
462         // SANITY: `libbpf` should guarantee NUL termination.
463         let name = util::c_char_slice_to_cstr(&s.name).unwrap();
464         let ty = MapType::from(s.type_);
465 
466         Some(Self {
467             name: name.to_owned(),
468             ty,
469             id: s.id,
470             key_size: s.key_size,
471             value_size: s.value_size,
472             max_entries: s.max_entries,
473             map_flags: s.map_flags,
474             ifindex: s.ifindex,
475             btf_vmlinux_value_type_id: s.btf_vmlinux_value_type_id,
476             netns_dev: s.netns_dev,
477             netns_ino: s.netns_ino,
478             btf_id: s.btf_id,
479             btf_key_type_id: s.btf_key_type_id,
480             btf_value_type_id: s.btf_value_type_id,
481         })
482     }
483 }
484 
485 gen_info_impl!(
486     /// Iterator that returns [`MapInfo`]s.
487     MapInfoIter,
488     MapInfo,
489     libbpf_sys::bpf_map_info,
490     libbpf_sys::bpf_map_get_next_id,
491     libbpf_sys::bpf_map_get_fd_by_id
492 );
493 
494 /// Information about BPF type format
495 #[derive(Debug, Clone)]
496 pub struct BtfInfo {
497     /// The name associated with this btf information in the kernel
498     pub name: CString,
499     /// The raw btf bytes from the kernel
500     pub btf: Vec<u8>,
501     /// The btf id associated with this btf information in the kernel
502     pub id: u32,
503 }
504 
505 impl BtfInfo {
load_from_fd(fd: BorrowedFd<'_>) -> Result<Self>506     fn load_from_fd(fd: BorrowedFd<'_>) -> Result<Self> {
507         let mut item = libbpf_sys::bpf_btf_info::default();
508         let mut btf: Vec<u8> = Vec::new();
509         let mut name: Vec<u8> = Vec::new();
510 
511         let item_ptr: *mut libbpf_sys::bpf_btf_info = &mut item;
512         let mut len = size_of_val(&item) as u32;
513 
514         let ret = unsafe {
515             libbpf_sys::bpf_obj_get_info_by_fd(fd.as_raw_fd(), item_ptr as *mut c_void, &mut len)
516         };
517         util::parse_ret(ret)?;
518 
519         // The API gives you the ascii string length while expecting
520         // you to give it back space for a nul-terminator
521         item.name_len += 1;
522         name.resize(item.name_len as usize, 0u8);
523         item.name = name.as_mut_ptr() as *mut c_void as u64;
524 
525         btf.resize(item.btf_size as usize, 0u8);
526         item.btf = btf.as_mut_ptr() as *mut c_void as u64;
527 
528         let ret = unsafe {
529             libbpf_sys::bpf_obj_get_info_by_fd(fd.as_raw_fd(), item_ptr as *mut c_void, &mut len)
530         };
531         util::parse_ret(ret)?;
532 
533         Ok(BtfInfo {
534             // SANITY: Our buffer contained space for a NUL byte and we set its
535             //         contents to 0. Barring a `libbpf` bug a NUL byte will be
536             //         present.
537             name: CString::from_vec_with_nul(name).unwrap(),
538             btf,
539             id: item.id,
540         })
541     }
542 }
543 
544 #[derive(Debug, Default)]
545 /// An iterator for the btf type information of modules and programs
546 /// in the kernel
547 pub struct BtfInfoIter {
548     cur_id: u32,
549 }
550 
551 impl BtfInfoIter {
552     // Returns Some(next_valid_fd), None on none left
next_valid_fd(&mut self) -> Option<OwnedFd>553     fn next_valid_fd(&mut self) -> Option<OwnedFd> {
554         loop {
555             if unsafe { libbpf_sys::bpf_btf_get_next_id(self.cur_id, &mut self.cur_id) } != 0 {
556                 return None;
557             }
558 
559             let fd = unsafe { libbpf_sys::bpf_btf_get_fd_by_id(self.cur_id) };
560             if fd < 0 {
561                 let err = io::Error::last_os_error();
562                 if err.kind() == io::ErrorKind::NotFound {
563                     continue;
564                 }
565                 return None;
566             }
567 
568             return Some(unsafe { OwnedFd::from_raw_fd(fd) });
569         }
570     }
571 }
572 
573 impl Iterator for BtfInfoIter {
574     type Item = BtfInfo;
575 
next(&mut self) -> Option<Self::Item>576     fn next(&mut self) -> Option<Self::Item> {
577         let fd = self.next_valid_fd()?;
578 
579         let info = BtfInfo::load_from_fd(fd.as_fd());
580 
581         match info {
582             Ok(i) => Some(i),
583             // TODO: We should consider bubbling up errors properly.
584             Err(_err) => None,
585         }
586     }
587 }
588 
589 #[derive(Debug, Clone)]
590 // TODO: Document members.
591 #[allow(missing_docs)]
592 pub struct RawTracepointLinkInfo {
593     pub name: String,
594 }
595 
596 #[derive(Debug, Clone)]
597 // TODO: Document members.
598 #[allow(missing_docs)]
599 pub struct TracingLinkInfo {
600     pub attach_type: ProgramAttachType,
601 }
602 
603 #[derive(Debug, Clone)]
604 // TODO: Document members.
605 #[allow(missing_docs)]
606 pub struct CgroupLinkInfo {
607     pub cgroup_id: u64,
608     pub attach_type: ProgramAttachType,
609 }
610 
611 #[derive(Debug, Clone)]
612 // TODO: Document members.
613 #[allow(missing_docs)]
614 pub struct NetNsLinkInfo {
615     pub ino: u32,
616     pub attach_type: ProgramAttachType,
617 }
618 
619 #[derive(Debug, Clone)]
620 // TODO: Document variants.
621 #[allow(missing_docs)]
622 pub enum LinkTypeInfo {
623     RawTracepoint(RawTracepointLinkInfo),
624     Tracing(TracingLinkInfo),
625     Cgroup(CgroupLinkInfo),
626     Iter,
627     NetNs(NetNsLinkInfo),
628     Unknown,
629 }
630 
631 /// Information about a BPF link
632 #[derive(Debug, Clone)]
633 // TODO: Document members.
634 #[allow(missing_docs)]
635 pub struct LinkInfo {
636     pub info: LinkTypeInfo,
637     pub id: u32,
638     pub prog_id: u32,
639 }
640 
641 impl LinkInfo {
from_uapi(fd: BorrowedFd<'_>, mut s: libbpf_sys::bpf_link_info) -> Option<Self>642     fn from_uapi(fd: BorrowedFd<'_>, mut s: libbpf_sys::bpf_link_info) -> Option<Self> {
643         let type_info = match s.type_ {
644             libbpf_sys::BPF_LINK_TYPE_RAW_TRACEPOINT => {
645                 let mut buf = [0; 256];
646                 s.__bindgen_anon_1.raw_tracepoint.tp_name = buf.as_mut_ptr() as u64;
647                 s.__bindgen_anon_1.raw_tracepoint.tp_name_len = buf.len() as u32;
648                 let item_ptr: *mut libbpf_sys::bpf_link_info = &mut s;
649                 let mut len = size_of_val(&s) as u32;
650 
651                 let ret = unsafe {
652                     libbpf_sys::bpf_obj_get_info_by_fd(
653                         fd.as_raw_fd(),
654                         item_ptr as *mut c_void,
655                         &mut len,
656                     )
657                 };
658                 if ret != 0 {
659                     return None;
660                 }
661 
662                 LinkTypeInfo::RawTracepoint(RawTracepointLinkInfo {
663                     name: util::c_ptr_to_string(
664                         unsafe { s.__bindgen_anon_1.raw_tracepoint.tp_name } as *const c_char,
665                     )
666                     .unwrap_or_else(|_| "?".to_string()),
667                 })
668             }
669             libbpf_sys::BPF_LINK_TYPE_TRACING => LinkTypeInfo::Tracing(TracingLinkInfo {
670                 attach_type: ProgramAttachType::from(unsafe {
671                     s.__bindgen_anon_1.tracing.attach_type
672                 }),
673             }),
674             libbpf_sys::BPF_LINK_TYPE_CGROUP => LinkTypeInfo::Cgroup(CgroupLinkInfo {
675                 cgroup_id: unsafe { s.__bindgen_anon_1.cgroup.cgroup_id },
676                 attach_type: ProgramAttachType::from(unsafe {
677                     s.__bindgen_anon_1.cgroup.attach_type
678                 }),
679             }),
680             libbpf_sys::BPF_LINK_TYPE_ITER => LinkTypeInfo::Iter,
681             libbpf_sys::BPF_LINK_TYPE_NETNS => LinkTypeInfo::NetNs(NetNsLinkInfo {
682                 ino: unsafe { s.__bindgen_anon_1.netns.netns_ino },
683                 attach_type: ProgramAttachType::from(unsafe {
684                     s.__bindgen_anon_1.netns.attach_type
685                 }),
686             }),
687             _ => LinkTypeInfo::Unknown,
688         };
689 
690         Some(Self {
691             info: type_info,
692             id: s.id,
693             prog_id: s.prog_id,
694         })
695     }
696 }
697 
698 gen_info_impl!(
699     /// Iterator that returns [`LinkInfo`]s.
700     LinkInfoIter,
701     LinkInfo,
702     libbpf_sys::bpf_link_info,
703     libbpf_sys::bpf_link_get_next_id,
704     libbpf_sys::bpf_link_get_fd_by_id
705 );
706