maps/xdp: make maps work on kernels not supporting ProgIds

On startup, the kernel is probed for support of chained program ids for
CpuMap, DevMap and DevMapHash, and will patch maps at load time to have
the proper size. Then, at runtime, the support is checked and will error
out if a program id is passed when the kernel does not support it.
reviewable/pr527/r2
Tuetuopay 2 years ago
parent d7086ab132
commit 990f45e894

@ -176,6 +176,14 @@ impl Map {
}
}
/// Set the value size in bytes
pub fn set_value_size(&mut self, size: u32) {
match self {
Map::Legacy(m) => m.def.value_size = size,
Map::Btf(m) => m.def.value_size = size,
}
}
/// Returns the max entry number
pub fn max_entries(&self) -> u32 {
match self {

@ -48,17 +48,24 @@ pub struct Features {
bpf_perf_link: bool,
bpf_global_data: bool,
bpf_cookie: bool,
cpumap_prog_id: bool,
devmap_prog_id: bool,
devmap_hash_prog_id: bool,
btf: Option<BtfFeatures>,
}
impl Features {
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub fn new(
bpf_name: bool,
bpf_probe_read_kernel: bool,
bpf_perf_link: bool,
bpf_global_data: bool,
bpf_cookie: bool,
cpumap_prog_id: bool,
devmap_prog_id: bool,
devmap_hash_prog_id: bool,
btf: Option<BtfFeatures>,
) -> Self {
Self {
@ -67,6 +74,9 @@ impl Features {
bpf_perf_link,
bpf_global_data,
bpf_cookie,
cpumap_prog_id,
devmap_prog_id,
devmap_hash_prog_id,
btf,
}
}
@ -96,6 +106,21 @@ impl Features {
self.bpf_cookie
}
/// Returns whether XDP CPU Maps support chained program IDs.
pub fn cpumap_prog_id(&self) -> bool {
self.cpumap_prog_id
}
/// Returns whether XDP Device Maps support chained program IDs.
pub fn devmap_prog_id(&self) -> bool {
self.devmap_prog_id
}
/// Returns whether XDP Hash Device Maps support chained program IDs.
pub fn devmap_hash_prog_id(&self) -> bool {
self.devmap_hash_prog_id
}
/// If BTF is supported, returns which BTF features are supported.
pub fn btf(&self) -> Option<&BtfFeatures> {
self.btf.as_ref()

@ -40,8 +40,8 @@ use crate::{
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_perf_link_supported,
is_probe_read_kernel_supported, is_prog_name_supported, retry_with_verifier_logs,
SyscallError,
is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported,
retry_with_verifier_logs, SyscallError,
},
util::{bytes_of, bytes_of_slice, possible_cpus, POSSIBLE_CPUS},
};
@ -94,6 +94,9 @@ fn detect_features() -> Features {
is_perf_link_supported(),
is_bpf_global_data_supported(),
is_bpf_cookie_supported(),
is_prog_id_supported(BPF_MAP_TYPE_CPUMAP),
is_prog_id_supported(BPF_MAP_TYPE_DEVMAP),
is_prog_id_supported(BPF_MAP_TYPE_DEVMAP_HASH),
btf,
);
debug!("BPF Feature Detection: {:#?}", f);
@ -477,6 +480,18 @@ impl<'a> BpfLoader<'a> {
}
}
}
match obj.map_type().try_into() {
Ok(BPF_MAP_TYPE_CPUMAP) => {
obj.set_value_size(if FEATURES.cpumap_prog_id() { 8 } else { 4 })
}
Ok(BPF_MAP_TYPE_DEVMAP) => {
obj.set_value_size(if FEATURES.devmap_prog_id() { 8 } else { 4 })
}
Ok(BPF_MAP_TYPE_DEVMAP_HASH) => {
obj.set_value_size(if FEATURES.devmap_hash_prog_id() { 8 } else { 4 })
}
_ => (),
}
let btf_fd = btf_fd.as_deref().map(|fd| fd.as_fd());
let mut map = match obj.pinning() {
PinningType::None => MapData::create(obj, &name, btf_fd)?,

@ -170,6 +170,10 @@ pub enum MapError {
error: PinError,
},
/// Program IDs are not supported
#[error("program ids are not supported by your kernel")]
ProgIdNotSupported,
/// Unsupported Map type
#[error("Unsupported map type found {map_type}")]
Unsupported {

@ -11,7 +11,7 @@ use crate::{
maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError},
programs::ProgramFd,
sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError},
Pod,
Pod, FEATURES,
};
/// An array of available CPUs.
@ -49,7 +49,12 @@ pub struct CpuMap<T> {
impl<T: Borrow<MapData>> CpuMap<T> {
pub(crate) fn new(map: T) -> Result<Self, MapError> {
let data = map.borrow();
check_kv_size::<u32, bpf_cpumap_val>(data)?;
if FEATURES.cpumap_prog_id() {
check_kv_size::<u32, bpf_cpumap_val>(data)?;
} else {
check_kv_size::<u32, u32>(data)?;
}
Ok(Self { inner: map })
}
@ -72,19 +77,25 @@ impl<T: Borrow<MapData>> CpuMap<T> {
check_bounds(data, cpu_index)?;
let fd = data.fd;
let value =
bpf_map_lookup_elem(fd, &cpu_index, flags).map_err(|(_, io_error)| SyscallError {
let value = if FEATURES.cpumap_prog_id() {
bpf_map_lookup_elem::<_, bpf_cpumap_val>(fd, &cpu_index, flags).map(|value| {
value.map(|value| CpuMapValue {
qsize: value.qsize,
// SAFETY: map writes use fd, map reads use id.
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6149
prog_id: unsafe { value.bpf_prog.id },
})
})
} else {
bpf_map_lookup_elem::<_, u32>(fd, &cpu_index, flags)
.map(|value| value.map(|qsize| CpuMapValue { qsize, prog_id: 0 }))
};
value
.map_err(|(_, io_error)| SyscallError {
call: "bpf_map_lookup_elem",
io_error,
})?;
let value: bpf_cpumap_val = value.ok_or(MapError::KeyNotFound)?;
// SAFETY: map writes use fd, map reads use id.
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6149
Ok(CpuMapValue {
qsize: value.qsize,
prog_id: unsafe { value.bpf_prog.id },
})
})?
.ok_or(MapError::KeyNotFound)
}
/// An iterator over the elements of the map. The iterator item type is `Result<u32,
@ -111,7 +122,8 @@ impl<T: BorrowMut<MapData>> CpuMap<T> {
/// # Errors
///
/// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
/// if `bpf_map_update_elem` fails.
/// if `bpf_map_update_elem` fails, [`MapError::ProgIdNotSupported`] if the kernel does not
/// support program ids and one is provided.
pub fn set(
&mut self,
cpu_index: u32,
@ -123,19 +135,26 @@ impl<T: BorrowMut<MapData>> CpuMap<T> {
check_bounds(data, cpu_index)?;
let fd = data.fd;
let value = bpf_cpumap_val {
qsize: queue_size,
bpf_prog: bpf_cpumap_val__bindgen_ty_1 {
fd: program
.map(|prog| prog.as_fd().as_raw_fd())
.unwrap_or_default(),
},
};
bpf_map_update_elem(fd, Some(&cpu_index), &value, flags).map_err(|(_, io_error)| {
SyscallError {
call: "bpf_map_update_elem",
io_error,
let res = if FEATURES.cpumap_prog_id() {
let value = bpf_cpumap_val {
qsize: queue_size,
bpf_prog: bpf_cpumap_val__bindgen_ty_1 {
fd: program
.map(|prog| prog.as_fd().as_raw_fd())
.unwrap_or_default(),
},
};
bpf_map_update_elem(fd, Some(&cpu_index), &value, flags)
} else {
if program.is_some() {
return Err(MapError::ProgIdNotSupported);
}
bpf_map_update_elem(fd, Some(&cpu_index), &queue_size, flags)
};
res.map_err(|(_, io_error)| SyscallError {
call: "bpf_map_update_elem",
io_error,
})?;
Ok(())
}

@ -11,7 +11,7 @@ use crate::{
maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError},
programs::ProgramFd,
sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError},
Pod,
Pod, FEATURES,
};
/// An array of network devices.
@ -43,7 +43,12 @@ pub struct DevMap<T> {
impl<T: Borrow<MapData>> DevMap<T> {
pub(crate) fn new(map: T) -> Result<Self, MapError> {
let data = map.borrow();
check_kv_size::<u32, bpf_devmap_val>(data)?;
if FEATURES.devmap_prog_id() {
check_kv_size::<u32, bpf_devmap_val>(data)?;
} else {
check_kv_size::<u32, u32>(data)?;
}
Ok(Self { inner: map })
}
@ -66,19 +71,29 @@ impl<T: Borrow<MapData>> DevMap<T> {
check_bounds(data, index)?;
let fd = data.fd;
let value =
bpf_map_lookup_elem(fd, &index, flags).map_err(|(_, io_error)| SyscallError {
let value = if FEATURES.devmap_prog_id() {
bpf_map_lookup_elem::<_, bpf_devmap_val>(fd, &index, flags).map(|value| {
value.map(|value| DevMapValue {
ifindex: value.ifindex,
// SAFETY: map writes use fd, map reads use id.
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6149
prog_id: unsafe { value.bpf_prog.id },
})
})
} else {
bpf_map_lookup_elem::<_, u32>(fd, &index, flags).map(|value| {
value.map(|ifindex| DevMapValue {
ifindex,
prog_id: 0,
})
})
};
value
.map_err(|(_, io_error)| SyscallError {
call: "bpf_map_lookup_elem",
io_error,
})?;
let value: bpf_devmap_val = value.ok_or(MapError::KeyNotFound)?;
// SAFETY: map writes use fd, map reads use id.
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6136
Ok(DevMapValue {
ifindex: value.ifindex,
prog_id: unsafe { value.bpf_prog.id },
})
})?
.ok_or(MapError::KeyNotFound)
}
/// An iterator over the elements of the array. The iterator item type is `Result<u32,
@ -104,7 +119,8 @@ impl<T: BorrowMut<MapData>> DevMap<T> {
/// # Errors
///
/// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
/// if `bpf_map_update_elem` fails.
/// if `bpf_map_update_elem` fails, [`MapError::ProgIdNotSupported`] if the kernel does not
/// support program ids and one is provided.
pub fn set(
&mut self,
index: u32,
@ -116,19 +132,26 @@ impl<T: BorrowMut<MapData>> DevMap<T> {
check_bounds(data, index)?;
let fd = data.fd;
let value = bpf_devmap_val {
ifindex,
bpf_prog: bpf_devmap_val__bindgen_ty_1 {
fd: program
.map(|prog| prog.as_fd().as_raw_fd())
.unwrap_or_default(),
},
};
bpf_map_update_elem(fd, Some(&index), &value, flags).map_err(|(_, io_error)| {
SyscallError {
call: "bpf_map_update_elem",
io_error,
let res = if FEATURES.devmap_prog_id() {
let value = bpf_devmap_val {
ifindex,
bpf_prog: bpf_devmap_val__bindgen_ty_1 {
fd: program
.map(|prog| prog.as_fd().as_raw_fd())
.unwrap_or_default(),
},
};
bpf_map_update_elem(fd, Some(&index), &value, flags)
} else {
if program.is_some() {
return Err(MapError::ProgIdNotSupported);
}
bpf_map_update_elem(fd, Some(&index), &ifindex, flags)
};
res.map_err(|(_, io_error)| SyscallError {
call: "bpf_map_update_elem",
io_error,
})?;
Ok(())
}

@ -11,6 +11,7 @@ use crate::{
maps::{check_kv_size, hash_map, IterableMap, MapData, MapError, MapIter, MapKeys},
programs::ProgramFd,
sys::{bpf_map_lookup_elem, SyscallError},
FEATURES,
};
use super::dev_map::DevMapValue;
@ -44,7 +45,12 @@ pub struct DevMapHash<T> {
impl<T: Borrow<MapData>> DevMapHash<T> {
pub(crate) fn new(map: T) -> Result<Self, MapError> {
let data = map.borrow();
check_kv_size::<u32, bpf_devmap_val>(data)?;
if FEATURES.devmap_hash_prog_id() {
check_kv_size::<u32, bpf_devmap_val>(data)?;
} else {
check_kv_size::<u32, u32>(data)?;
}
Ok(Self { inner: map })
}
@ -56,18 +62,30 @@ impl<T: Borrow<MapData>> DevMapHash<T> {
/// Returns [`MapError::SyscallError`] if `bpf_map_lookup_elem` fails.
pub fn get(&self, key: u32, flags: u64) -> Result<DevMapValue, MapError> {
let fd = self.inner.borrow().fd;
let value = bpf_map_lookup_elem(fd, &key, flags).map_err(|(_, io_error)| SyscallError {
call: "bpf_map_lookup_elem",
io_error,
})?;
let value: bpf_devmap_val = value.ok_or(MapError::KeyNotFound)?;
// SAFETY: map writes use fd, map reads use id.
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6136
Ok(DevMapValue {
ifindex: value.ifindex,
prog_id: unsafe { value.bpf_prog.id },
})
let value = if FEATURES.devmap_hash_prog_id() {
bpf_map_lookup_elem::<_, bpf_devmap_val>(fd, &key, flags).map(|value| {
value.map(|value| DevMapValue {
ifindex: value.ifindex,
// SAFETY: map writes use fd, map reads use id.
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6149
prog_id: unsafe { value.bpf_prog.id },
})
})
} else {
bpf_map_lookup_elem::<_, u32>(fd, &key, flags).map(|value| {
value.map(|ifindex| DevMapValue {
ifindex,
prog_id: 0,
})
})
};
value
.map_err(|(_, io_error)| SyscallError {
call: "bpf_map_lookup_elem",
io_error,
})?
.ok_or(MapError::KeyNotFound)
}
/// An iterator over the elements of the devmap in arbitrary order. The iterator item type is
@ -97,7 +115,9 @@ impl<T: BorrowMut<MapData>> DevMapHash<T> {
///
/// # Errors
///
/// Returns [`MapError::SyscallError`] if `bpf_map_update_elem` fails.
/// Returns [`MapError::SyscallError`] if `bpf_map_update_elem` fails,
/// [`MapError::ProgIdNotSupported`] if the kernel does not support program ids and one is
/// provided.
pub fn insert(
&mut self,
key: u32,
@ -105,15 +125,22 @@ impl<T: BorrowMut<MapData>> DevMapHash<T> {
program: Option<&ProgramFd>,
flags: u64,
) -> Result<(), MapError> {
let value = bpf_devmap_val {
ifindex,
bpf_prog: bpf_devmap_val__bindgen_ty_1 {
fd: program
.map(|prog| prog.as_fd().as_raw_fd())
.unwrap_or_default(),
},
};
hash_map::insert(self.inner.borrow_mut(), &key, &value, flags)
if FEATURES.devmap_hash_prog_id() {
let value = bpf_devmap_val {
ifindex,
bpf_prog: bpf_devmap_val__bindgen_ty_1 {
fd: program
.map(|prog| prog.as_fd().as_raw_fd())
.unwrap_or_default(),
},
};
hash_map::insert(self.inner.borrow_mut(), &key, &value, flags)
} else {
if program.is_some() {
return Err(MapError::ProgIdNotSupported);
}
hash_map::insert(self.inner.borrow_mut(), &key, &ifindex, flags)
}
}
/// Removes a value from the map.

@ -757,6 +757,31 @@ pub(crate) fn is_bpf_cookie_supported() -> bool {
bpf_prog_load(&mut attr).is_ok()
}
/// Tests whether CpuMap, DevMap and DevMapHash support program ids
pub(crate) fn is_prog_id_supported(map_type: bpf_map_type) -> bool {
assert!(
matches!(
map_type,
bpf_map_type::BPF_MAP_TYPE_CPUMAP
| bpf_map_type::BPF_MAP_TYPE_DEVMAP
| bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH
),
"is_prog_id_supported works for CpuMap, DevMap or DevMapHash",
);
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_1 };
u.map_type = map_type as u32;
u.key_size = 4;
u.value_size = 8; // 4 for CPU ID, 8 for CPU ID + prog ID
u.max_entries = 1;
u.map_flags = 0;
// SAFETY: BPF_MAP_CREATE returns a new file descriptor.
unsafe { fd_sys_bpf(bpf_cmd::BPF_MAP_CREATE, &mut attr) }.is_ok()
}
pub(crate) fn is_btf_supported() -> bool {
let mut btf = Btf::new();
let name_offset = btf.add_string("int");
@ -1036,4 +1061,33 @@ mod tests {
let supported = is_perf_link_supported();
assert!(!supported);
}
#[test]
fn test_prog_id_supported() {
override_syscall(|_call| Ok(42));
// Ensure that the three map types we can check are accepted
let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_CPUMAP);
assert!(supported);
let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_DEVMAP);
assert!(supported);
let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH);
assert!(supported);
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_MAP_CREATE,
..
} => Err((-1, io::Error::from_raw_os_error(EINVAL))),
_ => Ok(42),
});
let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_CPUMAP);
assert!(!supported);
}
#[test]
#[should_panic]
fn test_prog_id_supported_reject_types() {
is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_HASH);
}
}

Loading…
Cancel
Save