programs: rework ProgramError a bit

Move type specific errors to XdpError SocketFilterError etc.

Annotate all source errors with #[source]
pull/1/head
Alessandro Decina 4 years ago
parent f88ca1f1f1
commit d326038cf4

@ -164,7 +164,7 @@ pub enum BpfError {
#[error("error relocating BPF program `{program_name}`: {error}")] #[error("error relocating BPF program `{program_name}`: {error}")]
RelocationError { RelocationError {
program_name: String, program_name: String,
error: Box<dyn Error>, error: Box<dyn Error + Send + Sync>,
}, },
#[error("map error: {0}")] #[error("map error: {0}")]

@ -5,9 +5,7 @@ mod trace_point;
mod xdp; mod xdp;
use libc::{close, ENOSPC}; use libc::{close, ENOSPC};
use std::{ use std::{cell::RefCell, cmp, convert::TryFrom, ffi::CStr, io, os::raw::c_uint, rc::Rc};
cell::RefCell, cmp, convert::TryFrom, ffi::CStr, io, os::raw::c_uint, path::PathBuf, rc::Rc,
};
use thiserror::Error; use thiserror::Error;
use perf_attach::*; use perf_attach::*;
@ -19,65 +17,63 @@ pub use xdp::*;
use crate::{obj, sys::bpf_load_program, RawFd}; use crate::{obj, sys::bpf_load_program, RawFd};
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ProgramError { pub enum ProgramError {
#[error("the program {program} is already loaded")] #[error("the program is already loaded")]
AlreadyLoaded { program: String }, AlreadyLoaded,
#[error("the program {program} is not loaded")] #[error("the program is not loaded")]
NotLoaded { program: String }, NotLoaded,
#[error("the BPF_PROG_LOAD syscall for `{program}` failed: {io_error}\nVerifier output:\n{verifier_log}")]
LoadError {
program: String,
io_error: io::Error,
verifier_log: String,
},
#[error("the program was already detached")] #[error("the program was already detached")]
AlreadyDetached, AlreadyDetached,
#[error("the perf_event_open syscall failed: {io_error}")] #[error("the program is not attached")]
PerfEventOpenError { io_error: io::Error }, NotAttached,
#[error("PERF_EVENT_IOC_SET_BPF/PERF_EVENT_IOC_ENABLE failed: {io_error}")]
PerfEventAttachError { io_error: io::Error },
#[error("the program {program} is not attached")] #[error("the BPF_PROG_LOAD syscall failed: {io_error}\nVerifier output:\n{verifier_log}")]
NotAttached { program: String }, LoadError {
#[source]
io_error: io::Error,
verifier_log: String,
},
#[error("error attaching {program}: BPF_LINK_CREATE failed with {io_error}")] #[error("the perf_event_open syscall failed")]
BpfLinkCreateError { PerfEventOpenError {
program: String,
#[source] #[source]
io_error: io::Error, io_error: io::Error,
}, },
#[error("error attaching XDP program using netlink: {io_error}")] #[error("PERF_EVENT_IOC_SET_BPF/PERF_EVENT_IOC_ENABLE failed")]
NetlinkXdpError { PerfEventAttachError {
program: String,
#[source] #[source]
io_error: io::Error, io_error: io::Error,
}, },
#[error("unkown network interface {name}")] #[error("unknown network interface {name}")]
UnkownInterface { name: String }, UnknownInterface { name: String },
#[error("error reading ld.so.cache file")] #[error("BPF_LINK_CREATE failed")]
InvalidLdSoCache { error_kind: io::ErrorKind }, BpfLinkCreateError {
#[source]
io_error: io::Error,
},
#[error("could not resolve uprobe target {path}")] #[error("unexpected program type")]
InvalidUprobeTarget { path: PathBuf }, UnexpectedProgramType,
#[error("error resolving symbol: {error}")] #[error(transparent)]
UprobeSymbolError { symbol: String, error: String }, KProbeError(#[from] KProbeError),
#[error("setsockopt SO_ATTACH_BPF failed: {io_error}")] #[error(transparent)]
SocketFilterError { io_error: io::Error }, UProbeError(#[from] UProbeError),
#[error("unexpected program type")] #[error(transparent)]
UnexpectedProgramType, TracePointError(#[from] TracePointError),
#[error("{message}")] #[error(transparent)]
Other { message: String }, SocketFilterError(#[from] SocketFilterError),
#[error(transparent)]
XdpError(#[from] XdpError),
} }
pub trait ProgramFd { pub trait ProgramFd {
@ -140,9 +136,7 @@ pub(crate) struct ProgramData {
impl ProgramData { impl ProgramData {
fn fd_or_err(&self) -> Result<RawFd, ProgramError> { fn fd_or_err(&self) -> Result<RawFd, ProgramError> {
self.fd.ok_or(ProgramError::NotLoaded { self.fd.ok_or(ProgramError::NotLoaded)
program: self.name.clone(),
})
} }
pub fn link<T: Link + 'static>(&mut self, link: T) -> LinkRef { pub fn link<T: Link + 'static>(&mut self, link: T) -> LinkRef {
@ -204,11 +198,9 @@ impl VerifierLog {
} }
fn load_program(prog_type: c_uint, data: &mut ProgramData) -> Result<(), ProgramError> { fn load_program(prog_type: c_uint, data: &mut ProgramData) -> Result<(), ProgramError> {
let ProgramData { obj, fd, name, .. } = data; let ProgramData { obj, fd, .. } = data;
if fd.is_some() { if fd.is_some() {
return Err(ProgramError::AlreadyLoaded { return Err(ProgramError::AlreadyLoaded);
program: name.to_string(),
});
} }
let crate::obj::Program { let crate::obj::Program {
instructions, instructions,
@ -244,7 +236,6 @@ fn load_program(prog_type: c_uint, data: &mut ProgramData) -> Result<(), Program
if let Err((_, io_error)) = ret { if let Err((_, io_error)) = ret {
log_buf.truncate(); log_buf.truncate();
return Err(ProgramError::LoadError { return Err(ProgramError::LoadError {
program: name.clone(),
io_error, io_error,
verifier_log: log_buf.as_c_str().unwrap().to_string_lossy().to_string(), verifier_log: log_buf.as_c_str().unwrap().to_string_lossy().to_string(),
}); });

@ -1,12 +1,14 @@
use libc::pid_t; use libc::pid_t;
use object::{Object, ObjectSymbol}; use object::{Object, ObjectSymbol};
use std::{ use std::{
error::Error,
ffi::CStr, ffi::CStr,
fs, fs,
io::{self, BufRead, Cursor, Read}, io::{self, BufRead, Cursor, Read},
mem, mem,
os::raw::c_char, os::raw::c_char,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc,
}; };
use thiserror::Error; use thiserror::Error;
@ -16,11 +18,50 @@ use crate::{
sys::perf_event_open_probe, sys::perf_event_open_probe,
}; };
const LD_SO_CACHE_FILE: &str = "/etc/ld.so.cache";
lazy_static! { lazy_static! {
static ref LD_SO_CACHE: Result<LdSoCache, io::Error> = LdSoCache::load("/etc/ld.so.cache"); static ref LD_SO_CACHE: Result<LdSoCache, Arc<io::Error>> =
LdSoCache::load(LD_SO_CACHE_FILE).map_err(Arc::new);
} }
const LD_SO_CACHE_HEADER: &str = "glibc-ld.so.cache1.1"; const LD_SO_CACHE_HEADER: &str = "glibc-ld.so.cache1.1";
#[derive(Debug, Error)]
pub enum KProbeError {
#[error("`{filename}`")]
FileError {
filename: String,
#[source]
io_error: io::Error,
},
}
#[derive(Debug, Error)]
pub enum UProbeError {
#[error("error reading `{}` file", LD_SO_CACHE_FILE)]
InvalidLdSoCache {
#[source]
io_error: Arc<io::Error>,
},
#[error("could not resolve uprobe target `{path}`")]
InvalidTarget { path: PathBuf },
#[error("error resolving symbol")]
SymbolError {
symbol: String,
#[source]
error: Box<dyn Error + Send + Sync>,
},
#[error("`{filename}`")]
FileError {
filename: String,
#[source]
io_error: io::Error,
},
}
#[derive(Debug)] #[derive(Debug)]
pub struct KProbe { pub struct KProbe {
pub(crate) data: ProgramData, pub(crate) data: ProgramData,
@ -70,8 +111,9 @@ impl UProbe {
let target_str = &*target.as_os_str().to_string_lossy(); let target_str = &*target.as_os_str().to_string_lossy();
let mut path = if let Some(pid) = pid { let mut path = if let Some(pid) = pid {
find_lib_in_proc_maps(pid, &target_str).map_err(|io_error| ProgramError::Other { find_lib_in_proc_maps(pid, &target_str).map_err(|io_error| UProbeError::FileError {
message: format!("error parsing /proc/{}/maps: {}", pid, io_error), filename: format!("/proc/{}/maps", pid),
io_error,
})? })?
} else { } else {
None None
@ -84,22 +126,22 @@ impl UProbe {
let cache = let cache =
LD_SO_CACHE LD_SO_CACHE
.as_ref() .as_ref()
.map_err(|io_error| ProgramError::InvalidLdSoCache { .map_err(|error| UProbeError::InvalidLdSoCache {
error_kind: io_error.kind(), io_error: error.clone(),
})?; })?;
cache.resolve(target_str) cache.resolve(target_str)
} }
.map(String::from) .map(String::from)
}; };
let path = path.ok_or(ProgramError::InvalidUprobeTarget { let path = path.ok_or(UProbeError::InvalidTarget {
path: target.to_owned(), path: target.to_owned(),
})?; })?;
let sym_offset = if let Some(fn_name) = fn_name { let sym_offset = if let Some(fn_name) = fn_name {
resolve_symbol(&path, fn_name).map_err(|error| ProgramError::UprobeSymbolError { resolve_symbol(&path, fn_name).map_err(|error| UProbeError::SymbolError {
symbol: fn_name.to_string(), symbol: fn_name.to_string(),
error: error.to_string(), error: Box::new(error),
})? })?
} else { } else {
0 0
@ -131,13 +173,22 @@ fn attach(
) -> Result<LinkRef, ProgramError> { ) -> Result<LinkRef, ProgramError> {
use ProbeKind::*; use ProbeKind::*;
let perf_ty = read_sys_fs_perf_type(match kind { let perf_ty = match kind {
KProbe | KRetProbe => "kprobe", KProbe | KRetProbe => read_sys_fs_perf_type("kprobe")
UProbe | URetProbe => "uprobe", .map_err(|(filename, io_error)| KProbeError::FileError { filename, io_error })?,
})?; UProbe | URetProbe => read_sys_fs_perf_type("uprobe")
.map_err(|(filename, io_error)| UProbeError::FileError { filename, io_error })?,
};
let ret_bit = match kind { let ret_bit = match kind {
KRetProbe => Some(read_sys_fs_perf_ret_probe("kprobe")?), KRetProbe => Some(
URetProbe => Some(read_sys_fs_perf_ret_probe("uprobe")?), read_sys_fs_perf_ret_probe("kprobe")
.map_err(|(filename, io_error)| KProbeError::FileError { filename, io_error })?,
),
URetProbe => Some(
read_sys_fs_perf_ret_probe("uprobe")
.map_err(|(filename, io_error)| UProbeError::FileError { filename, io_error })?,
),
_ => None, _ => None,
}; };
@ -270,13 +321,13 @@ impl LdSoCache {
#[derive(Error, Debug)] #[derive(Error, Debug)]
enum ResolveSymbolError { enum ResolveSymbolError {
#[error("io error {0}")] #[error(transparent)]
Io(#[from] io::Error), Io(#[from] io::Error),
#[error("error parsing ELF {0}")] #[error("error parsing ELF")]
Object(#[from] object::Error), Object(#[from] object::Error),
#[error("unknown symbol {0}")] #[error("unknown symbol `{0}`")]
Unknown(String), Unknown(String),
} }
@ -291,34 +342,32 @@ fn resolve_symbol(path: &str, symbol: &str) -> Result<u64, ResolveSymbolError> {
.ok_or_else(|| ResolveSymbolError::Unknown(symbol.to_string())) .ok_or_else(|| ResolveSymbolError::Unknown(symbol.to_string()))
} }
fn read_sys_fs_perf_type(pmu: &str) -> Result<u32, ProgramError> { fn read_sys_fs_perf_type(pmu: &str) -> Result<u32, (String, io::Error)> {
let file = format!("/sys/bus/event_source/devices/{}/type", pmu); let file = format!("/sys/bus/event_source/devices/{}/type", pmu);
let perf_ty = fs::read_to_string(&file).map_err(|e| ProgramError::Other { let perf_ty = fs::read_to_string(&file).map_err(|e| (file.clone(), e))?;
message: format!("error parsing {}: {}", file, e),
})?;
let perf_ty = perf_ty let perf_ty = perf_ty
.trim() .trim()
.parse::<u32>() .parse::<u32>()
.map_err(|e| ProgramError::Other { .map_err(|e| (file, io::Error::new(io::ErrorKind::Other, e)))?;
message: format!("error parsing {}: {}", file, e),
})?;
Ok(perf_ty) Ok(perf_ty)
} }
fn read_sys_fs_perf_ret_probe(pmu: &str) -> Result<u32, ProgramError> { fn read_sys_fs_perf_ret_probe(pmu: &str) -> Result<u32, (String, io::Error)> {
let file = format!("/sys/bus/event_source/devices/{}/format/retprobe", pmu); let file = format!("/sys/bus/event_source/devices/{}/format/retprobe", pmu);
let data = fs::read_to_string(&file).map_err(|e| ProgramError::Other { let data = fs::read_to_string(&file).map_err(|e| (file.clone(), e))?;
message: format!("error parsing {}: {}", file, e),
})?;
let mut parts = data.trim().splitn(2, ":").skip(1); let mut parts = data.trim().splitn(2, ":").skip(1);
let config = parts.next().ok_or(ProgramError::Other { let config = parts.next().ok_or_else(|| {
message: format!("error parsing {}: `{}'", file, data), (
file.clone(),
io::Error::new(io::ErrorKind::Other, "invalid format"),
)
})?; })?;
config.parse::<u32>().map_err(|e| ProgramError::Other {
message: format!("error parsing {}: {}", file, e), config
}) .parse::<u32>()
.map_err(|e| (file, io::Error::new(io::ErrorKind::Other, e)))
} }

@ -1,11 +1,21 @@
use libc::{setsockopt, SOL_SOCKET, SO_ATTACH_BPF, SO_DETACH_BPF}; use libc::{setsockopt, SOL_SOCKET, SO_ATTACH_BPF, SO_DETACH_BPF};
use std::{io, mem, os::unix::prelude::RawFd}; use std::{io, mem, os::unix::prelude::RawFd};
use thiserror::Error;
use crate::{ use crate::{
generated::bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER, generated::bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER,
programs::{load_program, Link, LinkRef, ProgramData, ProgramError}, programs::{load_program, Link, LinkRef, ProgramData, ProgramError},
}; };
#[derive(Debug, Error)]
pub enum SocketFilterError {
#[error("setsockopt SO_ATTACH_BPF failed")]
SoAttachBpfError {
#[source]
io_error: io::Error,
},
}
#[derive(Debug)] #[derive(Debug)]
pub struct SocketFilter { pub struct SocketFilter {
pub(crate) data: ProgramData, pub(crate) data: ProgramData,
@ -29,9 +39,9 @@ impl SocketFilter {
) )
}; };
if ret < 0 { if ret < 0 {
return Err(ProgramError::SocketFilterError { return Err(SocketFilterError::SoAttachBpfError {
io_error: io::Error::last_os_error(), io_error: io::Error::last_os_error(),
}); })?;
} }
Ok(self.data.link(SocketFilterLink { Ok(self.data.link(SocketFilterLink {

@ -1,9 +1,20 @@
use std::fs; use std::{fs, io};
use thiserror::Error;
use crate::{generated::bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT, sys::perf_event_open_trace_point}; use crate::{generated::bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT, sys::perf_event_open_trace_point};
use super::{load_program, perf_attach, LinkRef, ProgramData, ProgramError}; use super::{load_program, perf_attach, LinkRef, ProgramData, ProgramError};
#[derive(Debug, Error)]
pub enum TracePointError {
#[error("`{filename}`")]
FileError {
filename: String,
#[source]
io_error: io::Error,
},
}
#[derive(Debug)] #[derive(Debug)]
pub struct TracePoint { pub struct TracePoint {
pub(crate) data: ProgramData, pub(crate) data: ProgramData,
@ -24,15 +35,20 @@ impl TracePoint {
} }
} }
fn read_sys_fs_trace_point_id(category: &str, name: &str) -> Result<u32, ProgramError> { fn read_sys_fs_trace_point_id(category: &str, name: &str) -> Result<u32, TracePointError> {
let file = format!("/sys/kernel/debug/tracing/events/{}/{}/id", category, name); let file = format!("/sys/kernel/debug/tracing/events/{}/{}/id", category, name);
let id = fs::read_to_string(&file).map_err(|e| ProgramError::Other { let id = fs::read_to_string(&file).map_err(|io_error| TracePointError::FileError {
message: format!("error parsing {}: {}", file, e), filename: file.clone(),
})?; io_error,
let id = id.trim().parse::<u32>().map_err(|e| ProgramError::Other {
message: format!("error parsing {}: {}", file, e),
})?; })?;
let id = id
.trim()
.parse::<u32>()
.map_err(|error| TracePointError::FileError {
filename: file.clone(),
io_error: io::Error::new(io::ErrorKind::Other, error),
})?;
Ok(id) Ok(id)
} }

@ -1,16 +1,25 @@
use std::ffi::CString;
use libc::if_nametoindex; use libc::if_nametoindex;
use std::{ffi::CString, io};
use thiserror::Error;
use crate::{generated::XDP_FLAGS_REPLACE, RawFd};
use crate::{ use crate::{
generated::{bpf_attach_type::BPF_XDP, bpf_prog_type::BPF_PROG_TYPE_XDP}, generated::{bpf_attach_type::BPF_XDP, bpf_prog_type::BPF_PROG_TYPE_XDP, XDP_FLAGS_REPLACE},
programs::{load_program, FdLink, Link, LinkRef, ProgramData, ProgramError}, programs::{load_program, FdLink, Link, LinkRef, ProgramData, ProgramError},
sys::bpf_link_create, sys::bpf_link_create,
sys::kernel_version, sys::kernel_version,
sys::netlink_set_xdp_fd, sys::netlink_set_xdp_fd,
RawFd,
}; };
#[derive(Debug, Error)]
pub enum XdpError {
#[error("netlink error while attaching XDP program")]
NetlinkError {
#[source]
io_error: io::Error,
},
}
#[derive(Debug)] #[derive(Debug)]
pub struct Xdp { pub struct Xdp {
pub(crate) data: ProgramData, pub(crate) data: ProgramData,
@ -31,30 +40,22 @@ impl Xdp {
let c_interface = CString::new(interface).unwrap(); let c_interface = CString::new(interface).unwrap();
let if_index = unsafe { if_nametoindex(c_interface.as_ptr()) } as RawFd; let if_index = unsafe { if_nametoindex(c_interface.as_ptr()) } as RawFd;
if if_index == 0 { if if_index == 0 {
return Err(ProgramError::UnkownInterface { return Err(ProgramError::UnknownInterface {
name: interface.to_string(), name: interface.to_string(),
})?; })?;
} }
let k_ver = kernel_version().unwrap(); let k_ver = kernel_version().unwrap();
if k_ver >= (5, 7, 0) { if k_ver >= (5, 7, 0) {
let link_fd = let link_fd = bpf_link_create(prog_fd, if_index + 42, BPF_XDP, 0)
bpf_link_create(prog_fd, if_index, BPF_XDP, 0).map_err(|(_, io_error)| { .map_err(|(_, io_error)| ProgramError::BpfLinkCreateError { io_error })?
ProgramError::BpfLinkCreateError { as RawFd;
program: self.name(),
io_error,
}
})? as RawFd;
Ok(self Ok(self
.data .data
.link(XdpLink::FdLink(FdLink { fd: Some(link_fd) }))) .link(XdpLink::FdLink(FdLink { fd: Some(link_fd) })))
} else { } else {
unsafe { netlink_set_xdp_fd(if_index, prog_fd, None, 0) }.map_err(|io_error| { unsafe { netlink_set_xdp_fd(if_index, prog_fd, None, 0) }
ProgramError::NetlinkXdpError { .map_err(|io_error| XdpError::NetlinkError { io_error })?;
program: self.name(),
io_error,
}
})?;
Ok(self.data.link(XdpLink::NlLink(NlLink { Ok(self.data.link(XdpLink::NlLink(NlLink {
if_index, if_index,

Loading…
Cancel
Save