use base64::Engine;
use deflate::deflate_bytes_zlib;
use libbpf_rs::libbpf_sys::{BPF_TC_CUSTOM, BPF_TC_EGRESS, BPF_TC_INGRESS};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_with::DefaultOnNull;
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct ExportedTypesStructMemberMeta {
pub name: String,
#[serde(rename = "type")]
pub ty: String,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct ExportedTypesStructMeta {
pub name: String,
pub members: Vec<ExportedTypesStructMemberMeta>,
pub size: u32,
pub type_id: u32,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default)]
pub enum SampleMapType {
#[serde(rename = "log2_hist")]
Log2Hist,
#[serde(rename = "linear_hist")]
LinearHist,
#[serde(rename = "default_kv")]
#[default]
DefaultKV,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct MapSampleMeta {
pub interval: usize,
#[serde(rename = "type", default)]
pub ty: SampleMapType,
#[serde(default = "default_helpers::map_unit_default")]
pub unit: String,
#[serde(default = "default_helpers::default_bool::<false>")]
pub clear_map: bool,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct OverridedStructMember {
pub name: String,
pub offset: usize,
pub btf_type_id: u32,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub enum MapExportConfig {
#[serde(rename = "no_export")]
NoExport,
#[serde(rename = "btf_type_id")]
ExportUseBtf(u32),
#[serde(rename = "custom_members")]
ExportUseCustomMembers(Vec<OverridedStructMember>),
#[serde(rename = "default")]
Default,
}
impl Default for MapExportConfig {
fn default() -> Self {
Self::NoExport
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct StackTraceFieldMapping {
#[serde(skip_serializing_if = "Option::is_none")]
pub pid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cpu_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comm: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kstack_sz: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ustack_sz: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kstack: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ustack: Option<String>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub enum BufferValueInterpreter {
#[serde(rename = "default_struct")]
DefaultStruct,
#[serde(rename = "stack_trace")]
StackTrace {
#[serde(default)]
field_map: StackTraceFieldMapping,
#[serde(default = "default_helpers::default_bool::<true>")]
with_symbols: bool,
},
}
impl Default for BufferValueInterpreter {
fn default() -> Self {
Self::DefaultStruct
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct MapMeta {
pub name: String,
pub ident: String,
#[serde(default = "default_helpers::default_bool::<false>")]
pub mmaped: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub sample: Option<MapSampleMeta>,
#[serde(default)]
pub export_config: MapExportConfig,
#[serde(default)]
pub intepreter: BufferValueInterpreter,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct ProgMeta {
pub name: String,
pub attach: String,
pub link: bool,
#[serde(flatten)]
pub others: Value,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct XDPProgExtraMeta {
#[serde(default = "default_helpers::default_i32::<1>")]
pub ifindex: i32,
#[serde(default = "default_helpers::default_u32::<0>")]
pub flags: u32,
#[serde(default)]
pub xdpopts: XDPOpts,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
#[derive(Default)]
pub struct XDPOpts {
#[serde(default = "default_helpers::default_i32::<0>")]
pub old_prog_fd: i32,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct TCProgExtraMeta {
#[serde(default)]
pub tchook: TCHook,
#[serde(default)]
pub tcopts: TCOpts,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct TCHook {
#[serde(default = "default_helpers::default_i32::<1>")]
pub ifindex: i32,
#[serde(default)]
pub attach_point: TCAttachPoint,
}
impl Default for TCHook {
fn default() -> Self {
Self {
ifindex: 1,
attach_point: TCAttachPoint::default(),
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub enum TCAttachPoint {
#[serde(rename = "BPF_TC_INGRESS")]
Ingress,
#[serde(rename = "BPF_TC_EGRESS")]
Egress,
#[serde(rename = "BPF_TC_CUSTOM")]
Custom,
}
impl Default for TCAttachPoint {
fn default() -> Self {
Self::Ingress
}
}
impl TCAttachPoint {
pub fn to_value(&self) -> u32 {
match self {
TCAttachPoint::Ingress => BPF_TC_INGRESS,
TCAttachPoint::Egress => BPF_TC_EGRESS,
TCAttachPoint::Custom => BPF_TC_CUSTOM,
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct TCOpts {
#[serde(default = "default_helpers::default_u32::<1>")]
pub handle: u32,
#[serde(default = "default_helpers::default_u32::<1>")]
pub priority: u32,
}
impl Default for TCOpts {
fn default() -> Self {
Self {
handle: 1,
priority: 1,
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct VariableCommandArgument {
#[serde(default)]
pub default: Option<Value>,
pub long: Option<String>,
pub short: Option<String>,
pub help: Option<String>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct DataSectionVariableMeta {
pub name: String,
#[serde(rename = "type")]
pub ty: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default)]
pub cmdarg: VariableCommandArgument,
#[serde(flatten)]
pub others: Value,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct DataSectionMeta {
pub name: String,
pub variables: Vec<DataSectionVariableMeta>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct BpfSkelDoc {
pub version: Option<String>,
pub brief: Option<String>,
pub details: Option<String>,
pub description: Option<String>,
}
#[serde_with::serde_as]
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct BpfSkeletonMeta {
pub data_sections: Vec<DataSectionMeta>,
pub maps: Vec<MapMeta>,
#[serde_as(deserialize_as = "DefaultOnNull")]
pub progs: Vec<ProgMeta>,
pub obj_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub doc: Option<BpfSkelDoc>,
}
impl BpfSkeletonMeta {
pub fn find_map_by_ident(&self, ident: impl AsRef<str>) -> Option<&MapMeta> {
let str_ref = ident.as_ref();
self.maps.iter().find(|s| s.ident == str_ref)
}
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
pub struct EunomiaObjectMeta {
#[serde(default)]
pub export_types: Vec<ExportedTypesStructMeta>,
pub bpf_skel: BpfSkeletonMeta,
#[serde(default = "default_helpers::default_usize::<64>")]
pub perf_buffer_pages: usize,
#[serde(default = "default_helpers::default_usize::<10>")]
pub perf_buffer_time_ms: usize,
#[serde(default = "default_helpers::default_i32::<100>")]
pub poll_timeout_ms: i32,
#[serde(default = "default_helpers::default_bool::<false>")]
pub debug_verbose: bool,
#[serde(default = "default_helpers::default_bool::<false>")]
pub print_header: bool,
#[serde(default = "default_helpers::default_bool::<false>")]
pub enable_multiple_export_types: bool,
}
#[derive(Deserialize, Serialize, PartialEq, Eq)]
pub(crate) struct ComposedObjectInner {
pub(crate) bpf_object: String,
pub(crate) bpf_object_size: usize,
pub(crate) meta: EunomiaObjectMeta,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ComposedObject {
pub bpf_object: Vec<u8>,
pub meta: EunomiaObjectMeta,
}
impl Serialize for ComposedObject {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::Error;
let bpf_object_size = self.bpf_object.len();
let compressed = deflate_bytes_zlib(&self.bpf_object);
let bpf_object_base64 = base64::engine::general_purpose::STANDARD.encode(compressed);
let json_val = serde_json::to_value(ComposedObjectInner {
bpf_object: bpf_object_base64,
bpf_object_size,
meta: self.meta.clone(),
})
.map_err(|e| Error::custom(format!("Failed to serialize: {e}")))?;
json_val.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ComposedObject {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let json_val: ComposedObjectInner =
serde_json::from_value(Value::deserialize(deserializer)?)
.map_err(|e| Error::custom(format!("Malformed json provided: {e}")))?;
let base64_decoded = base64::engine::general_purpose::STANDARD
.decode(&json_val.bpf_object)
.map_err(|e| Error::custom(format!("Malformed base64: {e}")))?;
let uncompressed = inflate::inflate_bytes_zlib(&base64_decoded)
.map_err(|e| Error::custom(format!("Malformed compressed data: {e}")))?;
if uncompressed.len() != json_val.bpf_object_size {
return Err(Error::custom(format!(
"Unmatched size: {} in the json, but {} in the decompressed file",
json_val.bpf_object_size,
uncompressed.len()
)));
}
Ok(Self {
bpf_object: uncompressed,
meta: json_val.meta,
})
}
}
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct RunnerConfig {
#[serde(default = "default_helpers::default_bool::<false>")]
pub print_kernel_debug: bool,
}
pub(crate) mod default_helpers {
pub(crate) fn default_bool<const V: bool>() -> bool {
V
}
pub(crate) fn default_usize<const V: usize>() -> usize {
V
}
pub(crate) fn default_i32<const V: i32>() -> i32 {
V
}
pub(crate) fn default_u32<const V: u32>() -> u32 {
V
}
pub(crate) fn map_unit_default() -> String {
"(unit)".into()
}
}
pub mod arg_builder;
pub mod arg_parser;
#[cfg(test)]
mod tests;