Skip to content

Commit

Permalink
maps/xdp: make maps work on kernels not supporting ProgIds
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Tuetuopay committed Sep 22, 2023
1 parent 35d84a6 commit abd0ad5
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 85 deletions.
8 changes: 8 additions & 0 deletions aya-obj/src/maps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
25 changes: 25 additions & 0 deletions aya-obj/src/obj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion aya/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ edition = "2021"
rust-version = "1.66"

[dependencies]
assert_matches = { workspace = true }
async-io = { workspace = true, optional = true }
aya-obj = { workspace = true, features = ["std"] }
bitflags = { workspace = true }
Expand All @@ -28,7 +29,6 @@ thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt"], optional = true }

[dev-dependencies]
assert_matches = { workspace = true }
tempfile = { workspace = true }

[features]
Expand Down
18 changes: 17 additions & 1 deletion aya/src/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ use crate::{
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,
is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported,
retry_with_verifier_logs,
},
util::{bytes_of, bytes_of_slice, possible_cpus, POSSIBLE_CPUS},
};
Expand Down Expand Up @@ -93,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);
Expand Down Expand Up @@ -476,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)?,
Expand Down
4 changes: 4 additions & 0 deletions aya/src/maps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ pub enum MapError {
error: PinError,
},

/// Program IDs are not supported
#[error("program ids are not supported by the current kernel")]
ProgIdNotSupported,

/// Unsupported Map type
#[error("Unsupported map type found {map_type}")]
Unsupported {
Expand Down
79 changes: 51 additions & 28 deletions aya/src/maps/xdp/cpu_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,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.
Expand Down Expand Up @@ -50,7 +50,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 })
}
Expand All @@ -73,19 +78,29 @@ impl<T: Borrow<MapData>> CpuMap<T> {
check_bounds(data, cpu_index)?;
let fd = data.fd().as_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://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6241
prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
})
})
} else {
bpf_map_lookup_elem::<_, u32>(fd, &cpu_index, flags).map(|value| {
value.map(|qsize| CpuMapValue {
qsize,
prog_id: None,
})
})
};
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://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6241
Ok(CpuMapValue {
qsize: value.qsize,
prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
})
})?
.ok_or(MapError::KeyNotFound)
}

/// An iterator over the elements of the map.
Expand All @@ -111,7 +126,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,
Expand All @@ -123,21 +139,28 @@ impl<T: BorrowMut<MapData>> CpuMap<T> {
check_bounds(data, cpu_index)?;
let fd = data.fd().as_fd();

let value = bpf_cpumap_val {
qsize: queue_size,
bpf_prog: bpf_cpumap_val__bindgen_ty_1 {
// Default is valid as the kernel will only consider fd > 0:
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/cpumap.c#L466
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 {
// Default is valid as the kernel will only consider fd > 0:
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/cpumap.c#L466
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(())
}
Expand Down
81 changes: 52 additions & 29 deletions aya/src/maps/xdp/dev_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,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.
Expand Down Expand Up @@ -44,7 +44,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 })
}
Expand All @@ -67,19 +72,29 @@ impl<T: Borrow<MapData>> DevMap<T> {
check_bounds(data, index)?;
let fd = data.fd().as_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://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228
prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
})
})
} else {
bpf_map_lookup_elem::<_, u32>(fd, &index, flags).map(|value| {
value.map(|ifindex| DevMapValue {
ifindex,
prog_id: None,
})
})
};
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://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228
Ok(DevMapValue {
ifindex: value.ifindex,
prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }),
})
})?
.ok_or(MapError::KeyNotFound)
}

/// An iterator over the elements of the array.
Expand All @@ -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,
Expand All @@ -116,22 +132,29 @@ impl<T: BorrowMut<MapData>> DevMap<T> {
check_bounds(data, index)?;
let fd = data.fd().as_fd();

let value = bpf_devmap_val {
ifindex,
bpf_prog: bpf_devmap_val__bindgen_ty_1 {
// Default is valid as the kernel will only consider fd > 0:
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918
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 {
// Default is valid as the kernel will only consider fd > 0:
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866
// https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918
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(())
}
Expand Down
Loading

0 comments on commit abd0ad5

Please sign in to comment.