mirror of https://github.com/aya-rs/aya
				
				
				
			aya,aya-obj: add feature probing program type
Adds API that probes whether kernel supports a program type.reviewable/pr1063/r25
							parent
							
								
									0237e36dbe
								
							
						
					
					
						commit
						d88f9cdd0e
					
				| @ -0,0 +1,111 @@ | ||||
| //! Probes and identifies available eBPF features supported by the host kernel.
 | ||||
| 
 | ||||
| use aya_obj::btf::{Btf, BtfKind}; | ||||
| use libc::{E2BIG, EINVAL}; | ||||
| 
 | ||||
| use super::{SyscallError, bpf_prog_load, with_trivial_prog}; | ||||
| use crate::programs::{ProgramError, ProgramType}; | ||||
| 
 | ||||
| /// Whether the host kernel supports the [`ProgramType`].
 | ||||
| ///
 | ||||
| /// # Examples
 | ||||
| ///
 | ||||
| /// ```no_run
 | ||||
| /// # use aya::{programs::ProgramType, sys::is_program_supported};
 | ||||
| /// #
 | ||||
| /// match is_program_supported(ProgramType::Xdp) {
 | ||||
| ///     Ok(true) => println!("XDP supported :)"),
 | ||||
| ///     Ok(false) => println!("XDP not supported :("),
 | ||||
| ///     Err(err) => println!("Uh oh! Unexpected error: {:?}", err),
 | ||||
| /// }
 | ||||
| /// ```
 | ||||
| ///
 | ||||
| /// # Errors
 | ||||
| ///
 | ||||
| /// Returns [`ProgramError::SyscallError`] if a syscall fails with an unexpected
 | ||||
| /// error, or [`ProgramError::Btf`] for BTF related errors.
 | ||||
| ///
 | ||||
| /// Certain errors are expected and handled internally; only unanticipated
 | ||||
| /// failures during probing will result in these errors.
 | ||||
| pub fn is_program_supported(program_type: ProgramType) -> Result<bool, ProgramError> { | ||||
|     if program_type == ProgramType::Unspecified { | ||||
|         return Ok(false); | ||||
|     } | ||||
| 
 | ||||
|     let mut verifier_log = [0_u8; 136]; | ||||
|     // First aim for a valid bpf_prog_load using these funcs for tracing & lsm.
 | ||||
|     // If symbols can't be retrieved from BTF, then leave unset and defer to verifier logs.
 | ||||
|     let attach_btf_id = match program_type { | ||||
|         // `bpf_fentry_test1` symbol from https://elixir.bootlin.com/linux/v5.5/source/net/bpf/test_run.c#L112
 | ||||
|         ProgramType::Tracing => Some("bpf_fentry_test1"), | ||||
|         // `bpf_lsm_bpf` symbol from https://elixir.bootlin.com/linux/v5.7/source/include/linux/lsm_hook_defs.h#L364
 | ||||
|         // or https://elixir.bootlin.com/linux/v5.11/source/kernel/bpf/bpf_lsm.c#L135 on later versions
 | ||||
|         ProgramType::Lsm => Some("bpf_lsm_bpf"), | ||||
|         _ => None, | ||||
|     } | ||||
|     .map(|func_name| { | ||||
|         Btf::from_sys_fs() | ||||
|             .and_then(|btf| btf.id_by_type_name_kind(func_name, BtfKind::Func)) | ||||
|             .unwrap_or(0) | ||||
|     }); | ||||
| 
 | ||||
|     let error = match with_trivial_prog(program_type, |attr| { | ||||
|         // SAFETY: union access
 | ||||
|         let u = unsafe { &mut attr.__bindgen_anon_3 }; | ||||
| 
 | ||||
|         if let Some(attach_btf_id) = attach_btf_id { | ||||
|             u.attach_btf_id = attach_btf_id; | ||||
|         } | ||||
|         match program_type { | ||||
|             // Use verifier log to detect support if loading fails.
 | ||||
|             // Loading *may* fail for tracing & lsm if func symbols cannot be found in BTF.
 | ||||
|             // Loading for extension is intentionally expected to fail.
 | ||||
|             ProgramType::Tracing | ProgramType::Extension | ProgramType::Lsm => { | ||||
|                 u.log_buf = verifier_log.as_mut_ptr() as u64; | ||||
|                 u.log_level = 1; | ||||
|                 u.log_size = verifier_log.len() as u32; | ||||
|             } | ||||
|             _ => {} | ||||
|         } | ||||
| 
 | ||||
|         bpf_prog_load(attr).err().map(|io_error| { | ||||
|             ProgramError::SyscallError(SyscallError { | ||||
|                 call: "bpf_prog_load", | ||||
|                 io_error, | ||||
|             }) | ||||
|         }) | ||||
|     }) { | ||||
|         Some(err) => err, | ||||
|         None => return Ok(true), | ||||
|     }; | ||||
| 
 | ||||
|     // Loading may fail for some types (namely tracing, extension, lsm, & struct_ops), so we
 | ||||
|     // perform additional examination on the OS error and/or verifier logs.
 | ||||
|     match &error { | ||||
|         ProgramError::SyscallError(err) => { | ||||
|             match err.io_error.raw_os_error() { | ||||
|                 // For most types, `EINVAL` typically indicates it is not supported.
 | ||||
|                 // However, further examination is required for tracing, extension, and lsm.
 | ||||
|                 Some(EINVAL) => { | ||||
|                     // When `attach_btf_id` is unset for types that require it, the following
 | ||||
|                     // message is written to logs.
 | ||||
|                     // Message comes from `check_attach_btf_id()` https://elixir.bootlin.com/linux/v5.5/source/kernel/bpf/verifier.c#L9535,
 | ||||
|                     // or `bpf_check_attach_target()` https://elixir.bootlin.com/linux/v5.9/source/kernel/bpf/verifier.c#L10849 on later versions
 | ||||
|                     let supported = matches!( | ||||
|                         program_type, | ||||
|                         ProgramType::Tracing | ProgramType::Extension | ProgramType::Lsm | ||||
|                             if verifier_log.starts_with(b"Tracing programs must provide btf_id")); | ||||
|                     Ok(supported) | ||||
|                 } | ||||
|                 Some(E2BIG) => Ok(false), | ||||
|                 // `ENOTSUPP` from `check_struct_ops_btf_id()` https://elixir.bootlin.com/linux/v5.6/source/kernel/bpf/verifier.c#L9740
 | ||||
|                 // indicates that it reached the verifier section, meaning the kernel is at least
 | ||||
|                 // aware of the type's existence.  Otherwise, it will produce `EINVAL`, meaning the
 | ||||
|                 // type is immediately rejected and does not exist.
 | ||||
|                 Some(524) if program_type == ProgramType::StructOps => Ok(true), | ||||
|                 _ => Err(error), | ||||
|             } | ||||
|         } | ||||
|         _ => Err(error), | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,132 @@ | ||||
| //! Test feature probing against kernel version.
 | ||||
| 
 | ||||
| use aya::{Btf, programs::ProgramType, sys::is_program_supported, util::KernelVersion}; | ||||
| use procfs::kernel_config; | ||||
| 
 | ||||
| use crate::utils::kernel_assert; | ||||
| 
 | ||||
| #[test] | ||||
| fn probe_supported_programs() { | ||||
|     let mut kern_version: KernelVersion; | ||||
|     let kernel_config = kernel_config().unwrap_or_default(); | ||||
|     macro_rules! is_supported { | ||||
|         ($prog_type:expr) => { | ||||
|             is_program_supported($prog_type).unwrap() | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(3, 19, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::SocketFilter), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 1, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::KProbe), kern_version); | ||||
|     kernel_assert!(is_supported!(ProgramType::SchedClassifier), kern_version); | ||||
|     kernel_assert!(is_supported!(ProgramType::SchedAction), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 7, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::TracePoint), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 8, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::Xdp), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 9, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::PerfEvent), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 10, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::CgroupSkb), kern_version); | ||||
|     kernel_assert!(is_supported!(ProgramType::CgroupSock), kern_version); | ||||
|     kernel_assert!(is_supported!(ProgramType::LwtInput), kern_version); | ||||
|     kernel_assert!(is_supported!(ProgramType::LwtOutput), kern_version); | ||||
|     kernel_assert!(is_supported!(ProgramType::LwtXmit), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 13, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::SockOps), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 14, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::SkSkb), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 15, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::CgroupDevice), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 17, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::SkMsg), kern_version); | ||||
|     kernel_assert!(is_supported!(ProgramType::RawTracePoint), kern_version); | ||||
|     kernel_assert!(is_supported!(ProgramType::CgroupSockAddr), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 18, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::LwtSeg6local), kern_version); | ||||
| 
 | ||||
|     // `lirc_mode2` requires CONFIG_BPF_LIRC_MODE2=y
 | ||||
|     let lirc_mode2_config = matches!( | ||||
|         kernel_config.get("CONFIG_BPF_LIRC_MODE2"), | ||||
|         Some(procfs::ConfigSetting::Yes) | ||||
|     ); | ||||
|     let lirc_mode2 = is_supported!(ProgramType::LircMode2); | ||||
|     kernel_assert!( | ||||
|         if aya::util::KernelVersion::current().unwrap() >= kern_version { | ||||
|             lirc_mode2 == lirc_mode2_config | ||||
|         } else { | ||||
|             lirc_mode2 | ||||
|         }, | ||||
|         kern_version | ||||
|     ); | ||||
|     if !lirc_mode2_config { | ||||
|         eprintln!("CONFIG_BPF_LIRC_MODE2 required for lirc_mode2 program type"); | ||||
|     } | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 19, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::SkReuseport), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(4, 20, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::FlowDissector), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(5, 2, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::CgroupSysctl), kern_version); | ||||
|     kernel_assert!( | ||||
|         is_supported!(ProgramType::RawTracePointWritable), | ||||
|         kern_version | ||||
|     ); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(5, 3, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::CgroupSockopt), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(5, 5, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::Tracing), kern_version); // Requires `CONFIG_DEBUG_INFO_BTF=y`
 | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(5, 6, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::StructOps), kern_version); | ||||
|     kernel_assert!(is_supported!(ProgramType::Extension), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(5, 7, 0); | ||||
|     // `lsm` requires `CONFIG_DEBUG_INFO_BTF=y` & `CONFIG_BPF_LSM=y`
 | ||||
|     // Ways to check if `CONFIG_BPF_LSM` is enabled:
 | ||||
|     // - kernel config has `CONFIG_BPF_LSM=y`, but config is not always exposed.
 | ||||
|     // - an LSM hooks is present in BTF, e.g. `bpf_lsm_bpf`. hooks are found in `bpf_lsm.c`
 | ||||
|     let lsm_enabled = matches!( | ||||
|         kernel_config.get("CONFIG_BPF_LSM"), | ||||
|         Some(procfs::ConfigSetting::Yes) | ||||
|     ) || Btf::from_sys_fs() | ||||
|         .and_then(|btf| btf.id_by_type_name_kind("bpf_lsm_bpf", aya_obj::btf::BtfKind::Func)) | ||||
|         .is_ok(); | ||||
|     let lsm = is_supported!(ProgramType::Lsm); | ||||
|     kernel_assert!( | ||||
|         if aya::util::KernelVersion::current().unwrap() >= kern_version { | ||||
|             lsm == lsm_enabled | ||||
|         } else { | ||||
|             lsm | ||||
|         }, | ||||
|         kern_version | ||||
|     ); | ||||
|     if !lsm_enabled { | ||||
|         eprintln!("CONFIG_BPF_LSM required for lsm program type"); | ||||
|     } | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(5, 9, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::SkLookup), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(5, 14, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::Syscall), kern_version); | ||||
| 
 | ||||
|     kern_version = KernelVersion::new(6, 4, 0); | ||||
|     kernel_assert!(is_supported!(ProgramType::Netfilter), kern_version); | ||||
| } | ||||
					Loading…
					
					
				
		Reference in New Issue