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