diff --git a/aya-obj/src/btf/btf.rs b/aya-obj/src/btf/btf.rs index 0d160a7d..91ac7e6d 100644 --- a/aya-obj/src/btf/btf.rs +++ b/aya-obj/src/btf/btf.rs @@ -18,11 +18,12 @@ use crate::{ info::{FuncSecInfo, LineSecInfo}, relocation::Relocation, Array, BtfEnum, BtfKind, BtfMember, BtfType, Const, Enum, FuncInfo, FuncLinkage, Int, - IntEncoding, LineInfo, Struct, Typedef, Union, VarLinkage, + IntEncoding, LineInfo, Struct, Typedef, Union, Var, VarLinkage, }, - generated::{btf_ext_header, btf_header}, + generated::{bpf_map_type, btf_ext_header, btf_header, BPF_F_RDONLY_PROG}, + maps::{bpf_map_def, LegacyMap}, util::{bytes_of, HashMap}, - Object, + EbpfSectionKind, Map, Object, }; pub(crate) const MAX_RESOLVE_DEPTH: u8 = 32; @@ -157,6 +158,20 @@ pub enum BtfError { /// unable to get symbol name #[error("Unable to get symbol name")] InvalidSymbolName, + + /// external symbol is invalid + #[error("Invalid extern symbol `{symbol_name}`")] + InvalidExternalSymbol { + /// name of the symbol + symbol_name: String, + }, + + /// external symbol not found + #[error("Extern symbol not found `{symbol_name}`")] + ExternalSymbolNotFound { + /// name of the symbol + symbol_name: String, + }, } /// Available BTF features @@ -463,6 +478,57 @@ impl Btf { }) } + pub(crate) fn type_align(&self, root_type_id: u32) -> Result { + let mut type_id = root_type_id; + for _ in 0..MAX_RESOLVE_DEPTH { + let ty = self.types.type_by_id(type_id)?; + let size = match ty { + BtfType::Array(Array { array, .. }) => { + type_id = array.element_type; + continue; + } + BtfType::Struct(Struct { size, members, .. }) + | BtfType::Union(Union { size, members, .. }) => { + let mut max_align = 1; + + for m in members { + let align = self.type_align(m.btf_type)?; + max_align = usize::max(align, max_align); + + if ty.member_bit_field_size(m).unwrap() == 0 + || m.offset % (8 * align as u32) != 0 + { + return Ok(1); + } + } + + if size % max_align as u32 != 0 { + return Ok(1); + } + + return Ok(max_align); + } + + other => { + if let Some(size) = other.size() { + u32::min(BtfType::ptr_size(), size) + } else if let Some(next) = other.btf_type() { + type_id = next; + continue; + } else { + return Err(BtfError::UnexpectedBtfType { type_id }); + } + } + }; + + return Ok(size as usize); + } + + Err(BtfError::MaximumTypeDepthReached { + type_id: root_type_id, + }) + } + /// Encodes the metadata as BTF format pub fn to_bytes(&self) -> Vec { // Safety: btf_header is POD @@ -473,6 +539,37 @@ impl Btf { buf } + pub(crate) fn get_extern_data_sec_entry_info( + &self, + target_var_name: &str, + ) -> Result<(String, Var), BtfError> { + for t in &self.types.types { + if let BtfType::DataSec(d) = t { + let sec_name = self.string_at(d.name_offset)?; + + for d in &d.entries { + if let BtfType::Var(var) = self.types.type_by_id(d.btf_type)? { + let var_name = self.string_at(var.name_offset)?; + + if target_var_name == var_name { + if var.linkage != VarLinkage::Extern { + return Err(BtfError::InvalidExternalSymbol { + symbol_name: var_name.into(), + }); + } + + return Ok((sec_name.into(), var.clone())); + } + } + } + } + } + + Err(BtfError::ExternalSymbolNotFound { + symbol_name: target_var_name.into(), + }) + } + // This follows the same logic as libbpf's bpf_object__sanitize_btf() function. // https://github.com/libbpf/libbpf/blob/05f94ddbb837f5f4b3161e341eed21be307eaa04/src/libbpf.c#L2701 // @@ -610,6 +707,14 @@ impl Btf { } }; e.offset = *offset as u32; + + if var.linkage == VarLinkage::Extern { + let mut var = var.clone(); + var.linkage = VarLinkage::Global; + + types.types[e.btf_type as usize] = BtfType::Var(var); + } + debug!( "{} {}: VAR {}: fixup offset {}", kind, name, var_name, offset @@ -730,6 +835,107 @@ impl Default for Btf { } impl Object { + fn patch_extern_data_internal( + &mut self, + externs: &HashMap>, + ) -> Result)>, BtfError> { + if let Some(ref mut obj_btf) = &mut self.btf { + if obj_btf.is_empty() { + return Ok(None); + } + + let mut kconfig_map_index = 0; + + for map in self.maps.values() { + if map.section_index() >= kconfig_map_index { + kconfig_map_index = map.section_index() + 1; + } + } + + let kconfig_map_index = self.maps.len(); + + let symbols = self + .symbol_table + .iter_mut() + .filter(|(_, s)| s.name.is_some() && s.section_index.is_none() && s.is_external) + .map(|(_, s)| (s.name.as_ref().unwrap().clone(), s)); + + let mut section_data = Vec::::new(); + let mut offset = 0u64; + let mut has_extern_data = false; + + for (name, symbol) in symbols { + let (datasec_name, var) = obj_btf.get_extern_data_sec_entry_info(&name)?; + + if datasec_name == ".kconfig" { + has_extern_data = true; + + let type_size = obj_btf.type_size(var.btf_type)?; + let type_align = obj_btf.type_align(var.btf_type)? as u64; + + let mut external_value_opt = externs.get(&name); + let empty_data = vec![0; type_size]; + + if external_value_opt.is_none() && symbol.is_weak { + external_value_opt = Some(&empty_data); + } + + if let Some(data) = external_value_opt { + symbol.address = (offset + (type_align - 1)) & !(type_align - 1); + symbol.size = type_size as u64; + symbol.section_index = Some(kconfig_map_index); + + section_data.resize((symbol.address - offset) as usize, 0); + + self.symbol_offset_by_name.insert(name, symbol.address); + section_data.extend(data); + offset = symbol.address + data.len() as u64; + } else { + return Err(BtfError::ExternalSymbolNotFound { symbol_name: name }); + } + } + } + + if has_extern_data { + self.section_infos.insert( + ".kconfig".into(), + (SectionIndex(kconfig_map_index), section_data.len() as u64), + ); + + return Ok(Some((SectionIndex(kconfig_map_index), section_data))); + } + } + Ok(None) + } + + /// Patches extern data + pub fn patch_extern_data( + &mut self, + externs: &HashMap>, + ) -> Result<(), BtfError> { + if let Some((section_index, data)) = self.patch_extern_data_internal(externs)? { + self.maps.insert( + ".kconfig".into(), + Map::Legacy(LegacyMap { + def: bpf_map_def { + map_type: bpf_map_type::BPF_MAP_TYPE_ARRAY as u32, + key_size: mem::size_of::() as u32, + value_size: data.len() as u32, + max_entries: 1, + map_flags: BPF_F_RDONLY_PROG, + ..Default::default() + }, + section_index: section_index.0, + section_kind: EbpfSectionKind::Rodata, + symbol_index: None, + data, + }), + ); + } + + Ok(()) + } + /// Fixes up and sanitizes BTF data. /// /// Mostly, it removes unsupported types and works around LLVM behaviours. diff --git a/aya-obj/src/btf/types.rs b/aya-obj/src/btf/types.rs index 9e68b71c..3b75af76 100644 --- a/aya-obj/src/btf/types.rs +++ b/aya-obj/src/btf/types.rs @@ -1292,11 +1292,15 @@ impl BtfType { BtfType::Struct(t) => Some(t.size), BtfType::Union(t) => Some(t.size), BtfType::DataSec(t) => Some(t.size), - BtfType::Ptr(_) => Some(mem::size_of::<&()>() as u32), + BtfType::Ptr(_) => Some(Self::ptr_size()), _ => None, } } + pub(crate) fn ptr_size() -> u32 { + mem::size_of::<&()>() as u32 + } + pub(crate) fn btf_type(&self) -> Option { match self { BtfType::Const(t) => Some(t.btf_type), diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index e4be87da..4eb9f58e 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -49,6 +49,7 @@ pub struct Features { devmap_prog_id: bool, prog_info_map_ids: bool, prog_info_gpl_compatible: bool, + bpf_syscall_wrapper: bool, btf: Option, } @@ -65,6 +66,7 @@ impl Features { devmap_prog_id: bool, prog_info_map_ids: bool, prog_info_gpl_compatible: bool, + bpf_syscall_wrapper: bool, btf: Option, ) -> Self { Self { @@ -77,6 +79,7 @@ impl Features { devmap_prog_id, prog_info_map_ids, prog_info_gpl_compatible, + bpf_syscall_wrapper, btf, } } @@ -118,6 +121,10 @@ impl Features { pub fn devmap_prog_id(&self) -> bool { self.devmap_prog_id } + /// Returns whether BPF syscall wrapper hooking is supported. + pub fn bpf_syscall_wrapper(&self) -> bool { + self.bpf_syscall_wrapper + } /// Returns whether `bpf_prog_info` supports `nr_map_ids` & `map_ids` fields. pub fn prog_info_map_ids(&self) -> bool { @@ -483,6 +490,8 @@ impl Object { address: symbol.address(), size: symbol.size(), is_definition: symbol.is_definition(), + is_external: symbol.is_undefined() && (symbol.is_global() || symbol.is_weak()), + is_weak: symbol.is_weak(), kind: symbol.kind(), }; bpf_obj.symbol_table.insert(symbol.index().0, sym); @@ -1465,6 +1474,8 @@ mod tests { size, is_definition: false, kind: SymbolKind::Text, + is_external: false, + is_weak: false, }, ); obj.symbols_by_section @@ -2601,6 +2612,8 @@ mod tests { address: 0, size: 3, is_definition: true, + is_external: false, + is_weak: false, kind: SymbolKind::Data, }, ); diff --git a/aya-obj/src/relocation.rs b/aya-obj/src/relocation.rs index b05648ba..0c4e6643 100644 --- a/aya-obj/src/relocation.rs +++ b/aya-obj/src/relocation.rs @@ -99,6 +99,8 @@ pub(crate) struct Symbol { pub(crate) address: u64, pub(crate) size: u64, pub(crate) is_definition: bool, + pub(crate) is_external: bool, + pub(crate) is_weak: bool, pub(crate) kind: SymbolKind, } @@ -218,7 +220,9 @@ fn relocate_maps<'a, I: Iterator>( }; // calls and relocation to .text symbols are handled in a separate step - if insn_is_call(&instructions[ins_index]) || text_sections.contains(§ion_index) { + if insn_is_call(&instructions[ins_index]) + || (text_sections.contains(§ion_index) && !sym.is_external) + { continue; } @@ -367,10 +371,11 @@ impl<'a> FunctionLinker<'a> { // only consider text relocations, data relocations are // relocated in relocate_maps() sym.kind == SymbolKind::Text - || sym - .section_index - .map(|section_index| self.text_sections.contains(§ion_index)) - .unwrap_or(false) + || (!sym.is_external + && sym + .section_index + .map(|section_index| self.text_sections.contains(§ion_index)) + .unwrap_or(false)) }); // not a call and not a text relocation, we don't need to do anything @@ -510,6 +515,8 @@ mod test { address, size, is_definition: false, + is_external: false, + is_weak: false, kind: SymbolKind::Data, } } diff --git a/aya/Cargo.toml b/aya/Cargo.toml index 57e80c77..3a222200 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -23,7 +23,13 @@ libc = { workspace = true } log = { workspace = true } object = { workspace = true, features = ["elf", "read_core", "std", "write"] } thiserror = { workspace = true } -tokio = { workspace = true, features = ["rt"], optional = true } +tokio = { workspace = true, features = [ + "rt", + "macros", + "rt-multi-thread", + "net", +], optional = true } +flate2 = "1.0" [dev-dependencies] tempfile = { workspace = true } diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 8a189476..bb979a39 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -1,7 +1,8 @@ use std::{ borrow::Cow, collections::{HashMap, HashSet}, - fs, io, + fs::{self, File}, + io::{self, Read}, os::{ fd::{AsFd as _, AsRawFd as _}, raw::c_int, @@ -16,6 +17,8 @@ use aya_obj::{ relocation::EbpfRelocationError, EbpfSectionKind, Features, }; +use flate2::read::GzDecoder; +use lazy_static::lazy_static; use log::{debug, warn}; use thiserror::Error; @@ -36,14 +39,15 @@ use crate::{ SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp, }, sys::{ - bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported, - is_btf_datasec_supported, is_btf_decl_tag_supported, is_btf_enum64_supported, - is_btf_float_supported, is_btf_func_global_supported, is_btf_func_supported, - is_btf_supported, is_btf_type_tag_supported, is_info_gpl_compatible_supported, - is_info_map_ids_supported, is_perf_link_supported, is_probe_read_kernel_supported, - is_prog_id_supported, is_prog_name_supported, retry_with_verifier_logs, + self, bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported, + is_bpf_syscall_wrapper_supported, is_btf_datasec_supported, is_btf_decl_tag_supported, + is_btf_enum64_supported, is_btf_float_supported, is_btf_func_global_supported, + is_btf_func_supported, is_btf_supported, is_btf_type_tag_supported, + is_info_gpl_compatible_supported, is_info_map_ids_supported, is_perf_link_supported, + is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported, + retry_with_verifier_logs, }, - util::{bytes_of, bytes_of_slice, page_size, possible_cpus, POSSIBLE_CPUS}, + util::{bytes_of, bytes_of_slice, page_size, possible_cpus, KernelVersion, POSSIBLE_CPUS}, }; pub(crate) const BPF_OBJ_NAME_LEN: usize = 16; @@ -98,6 +102,7 @@ fn detect_features() -> Features { is_prog_id_supported(BPF_MAP_TYPE_DEVMAP), is_info_map_ids_supported(), is_info_gpl_compatible_supported(), + is_bpf_syscall_wrapper_supported(), btf, ); debug!("BPF Feature Detection: {:#?}", f); @@ -109,6 +114,122 @@ pub fn features() -> &'static Features { &FEATURES } +lazy_static! { + static ref KCONFIG_DEFINITION: HashMap> = compute_kconfig_definition(&FEATURES); +} + +fn to_bytes(value: u64) -> [u8; 8] { + if cfg!(target_endian = "big") { + value.to_be_bytes() + } else { + value.to_le_bytes() + } +} + +fn compute_kconfig_definition(features: &Features) -> HashMap> { + let mut result = HashMap::new(); + + if let Ok(KernelVersion { + major, + minor, + patch, + }) = KernelVersion::current() + { + result.insert( + "LINUX_KERNEL_VERSION".to_string(), + to_bytes((u64::from(major) << 16) + (u64::from(minor) << 8) + u64::from(patch)) + .to_vec(), + ); + } + + let bpf_cookie = if features.bpf_cookie() { 1u64 } else { 0u64 }; + let bpf_syscall_wrapper = if features.bpf_syscall_wrapper() { + 1u64 + } else { + 0u64 + }; + + result.insert( + "LINUX_HAS_BPF_COOKIE".to_string(), + to_bytes(bpf_cookie).to_vec(), + ); + + result.insert( + "LINUX_HAS_SYSCALL_WRAPPER".to_string(), + to_bytes(bpf_syscall_wrapper).to_vec(), + ); + + let mut raw_config_opt = None; + + let proc_config_path = PathBuf::from("/proc/config.gz"); + + if proc_config_path.exists() { + if let Ok(file) = File::open(proc_config_path) { + let mut file = GzDecoder::new(file); + let mut output = String::new(); + if file.read_to_string(&mut output).is_ok() { + raw_config_opt = Some(output); + } + } + } + + if raw_config_opt.is_none() { + if let Ok(release) = sys::kernel_release() { + let config_path = PathBuf::from("/boot").join(format!("config-{}", release)); + + if config_path.exists() { + if let Ok(mut file) = File::open(config_path) { + let mut output = String::new(); + if file.read_to_string(&mut output).is_ok() { + raw_config_opt = Some(output); + } + } + } + } + } + + if let Some(raw_config) = raw_config_opt { + for line in raw_config.split('\n') { + if !line.starts_with("CONFIG_") { + continue; + } + + let mut parts = line.split('='); + let (key, raw_value) = match (parts.next(), parts.next(), parts.count()) { + (Some(key), Some(value), 0) => (key, value), + _ => continue, + }; + + let value = match raw_value.chars().next() { + Some('n') => to_bytes(0).to_vec(), + Some('y') => to_bytes(1).to_vec(), + Some('m') => to_bytes(2).to_vec(), + Some('"') => { + if raw_value.len() > 2 || raw_value.ends_with('"') { + continue; + } + + let raw_value = &raw_value[1..raw_value.len() - 1]; + + raw_value.as_bytes().to_vec() + } + Some(_) => { + if let Ok(value) = raw_value.parse::() { + to_bytes(value).to_vec() + } else { + continue; + } + } + None => continue, + }; + + result.insert(key.to_string(), value); + } + } + + result +} + /// Builder style API for advanced loading of eBPF programs. /// /// Loading eBPF code involves a few steps, including loading maps and applying @@ -399,6 +520,7 @@ impl<'a> EbpfLoader<'a> { } = self; let mut obj = Object::parse(data)?; obj.patch_map_data(globals.clone())?; + obj.patch_extern_data(&KCONFIG_DEFINITION)?; let btf_fd = if let Some(features) = &FEATURES.btf() { if let Some(btf) = obj.fixup_and_sanitize_btf(features)? { diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index d0ce82d9..358991bb 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -38,7 +38,6 @@ // modules we don't export mod info; -mod probe; mod utils; // modules we explicitly export so their pub items (Links etc) get exported too @@ -57,6 +56,7 @@ pub mod lirc_mode2; pub mod lsm; pub mod perf_attach; pub mod perf_event; +pub mod probe; pub mod raw_trace_point; pub mod sk_lookup; pub mod sk_msg; diff --git a/aya/src/programs/probe.rs b/aya/src/programs/probe.rs index 4d737e34..2b292066 100644 --- a/aya/src/programs/probe.rs +++ b/aya/src/programs/probe.rs @@ -1,3 +1,5 @@ +//! Probes. + use std::{ ffi::{OsStr, OsString}, fmt::Write as _, @@ -145,7 +147,7 @@ pub(crate) fn detach_debug_fs(event: ProbeEvent) -> Result<(), ProgramError> { }) } -fn create_as_probe( +pub(crate) fn create_as_probe( kind: ProbeKind, fn_name: &OsStr, offset: u64, diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 8d07b70c..3fb59054 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -1,6 +1,6 @@ use std::{ cmp, - ffi::{c_char, CStr, CString}, + ffi::{c_char, CStr, CString, OsStr}, io, iter, mem::{self, MaybeUninit}, os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, FromRawFd as _, RawFd}, @@ -30,6 +30,7 @@ use crate::{ }, copy_instructions, }, + programs::probe::create_as_probe, sys::{syscall, SysResult, Syscall, SyscallError}, util::KernelVersion, Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN, FEATURES, @@ -948,6 +949,49 @@ pub(crate) fn is_prog_id_supported(map_type: bpf_map_type) -> bool { fd.is_ok() } +fn arch_specific_syscall_prefix() -> Option<&'static str> { + if cfg!(target_arch = "aarch64") { + Some("arm64") + } else if cfg!(target_arch = "arm") { + Some("arm") + } else if cfg!(target_arch = "powerpc") { + Some("powerpc") + } else if cfg!(target_arch = "powerpc64") { + Some("powerpc64") + } else if cfg!(target_arch = "riscv32") || cfg!(target_arch = "riscv64") { + Some("riscv") + } else if cfg!(target_arch = "x86") { + Some("ia32") + } else if cfg!(target_arch = "x86_64") { + Some("x64") + } else if cfg!(target_arch = "s390x") { + Some("s390x") + } else if cfg!(target_arch = "mips") || cfg!(target_arch = "mips64") { + Some("mips") + } else { + None + } +} + +pub(crate) fn is_bpf_syscall_wrapper_supported() -> bool { + let syscall_prefix_opt = arch_specific_syscall_prefix(); + + if let Some(syscall_prefix) = syscall_prefix_opt { + let syscall_name = format!("__{}_sys_bpf", syscall_prefix); + let syscall_name = OsStr::new(syscall_name.as_str()); + + if let Ok(fd) = create_as_probe(crate::programs::ProbeKind::KProbe, syscall_name, 0, None) { + unsafe { + libc::close(fd.as_raw_fd()); + } + + return true; + } + } + + false +} + pub(crate) fn is_btf_supported() -> bool { let mut btf = Btf::new(); let name_offset = btf.add_string("int"); diff --git a/aya/src/sys/mod.rs b/aya/src/sys/mod.rs index 05f8b4dd..7dc4f6d0 100644 --- a/aya/src/sys/mod.rs +++ b/aya/src/sys/mod.rs @@ -191,3 +191,26 @@ impl From for crate::generated::bpf_stats_type { pub fn enable_stats(stats_type: Stats) -> Result { bpf_enable_stats(stats_type.into()).map(|fd| fd.into_inner()) } + +#[cfg(test)] +pub(crate) fn kernel_release() -> Result { + Ok("unknown".to_string()) +} + +#[cfg(not(test))] +pub(crate) fn kernel_release() -> Result { + use std::ffi::CStr; + + use libc::utsname; + + unsafe { + let mut v = mem::zeroed::(); + if libc::uname(&mut v as *mut _) != 0 { + return Err(()); + } + + let release = CStr::from_ptr(v.release.as_ptr()); + + Ok(release.to_string_lossy().into_owned()) + } +} diff --git a/aya/src/util.rs b/aya/src/util.rs index 18de6002..5a819c56 100644 --- a/aya/src/util.rs +++ b/aya/src/util.rs @@ -23,9 +23,12 @@ use crate::{ // Adapted from https://docs.rs/procfs/latest/procfs/sys/kernel/struct.Version.html. #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd)] pub struct KernelVersion { - pub(crate) major: u8, - pub(crate) minor: u8, - pub(crate) patch: u16, + /// Major version. + pub major: u8, + /// Minor version. + pub minor: u8, + /// Patch version. + pub patch: u16, } #[derive(thiserror::Error, Debug)] diff --git a/test/integration-test/bpf/kconfig.bpf.c b/test/integration-test/bpf/kconfig.bpf.c new file mode 100644 index 00000000..4fe45c6d --- /dev/null +++ b/test/integration-test/bpf/kconfig.bpf.c @@ -0,0 +1,15 @@ +#include +#include + +extern unsigned int CONFIG_BPF __kconfig; + +SEC("xdp/pass") +int xdp_pass(struct xdp_md *ctx) { + if (!CONFIG_BPF) { + return XDP_DROP; + } + + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; diff --git a/test/integration-test/build.rs b/test/integration-test/build.rs index 128f25e4..56d1b523 100644 --- a/test/integration-test/build.rs +++ b/test/integration-test/build.rs @@ -72,6 +72,7 @@ fn main() { ("multimap-btf.bpf.c", false), ("reloc.bpf.c", true), ("text_64_64_reloc.c", false), + ("kconfig.bpf.c", false), // TODO: or true ? ]; if build_integration_bpf { diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index fc31e6c2..e3258536 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -9,6 +9,7 @@ pub const RELOC_BTF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.target.o")); pub const TEXT_64_64_RELOC: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/text_64_64_reloc.o")); +pub const KCONFIG: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/kconfig.bpf.o")); pub const LOG: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/log")); pub const MAP_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/map_test")); diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs index 0d57aea9..d5580924 100644 --- a/test/integration-test/src/tests/smoke.rs +++ b/test/integration-test/src/tests/smoke.rs @@ -69,3 +69,16 @@ fn extension() { .load(pass.fd().unwrap().try_clone().unwrap(), "xdp_pass") .unwrap(); } + +#[test] +fn kconfig() { + let kernel_version = KernelVersion::current().unwrap(); + if kernel_version < KernelVersion::new(5, 9, 0) { + eprintln!("skipping test on kernel {kernel_version:?}, XDP uses netlink"); + return; + } + let mut bpf = Ebpf::load(crate::KCONFIG).unwrap(); + let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); + pass.load().unwrap(); + pass.attach("lo", XdpFlags::default()).unwrap(); +}