use std::fmt;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LayerSet: u8 {
const EBPF = 0x1;
const PROXY = 0x2;
const SDK = 0x4;
}
}
impl LayerSet {
pub fn names(self) -> Vec<&'static str> {
let mut out = Vec::with_capacity(3);
if self.contains(Self::EBPF) {
out.push("ebpf");
}
if self.contains(Self::PROXY) {
out.push("proxy");
}
if self.contains(Self::SDK) {
out.push("sdk");
}
out
}
}
impl fmt::Display for LayerSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let names = self.names();
if names.is_empty() {
return write!(f, "none");
}
write!(f, "{}", names.join("+"))
}
}
fn check_kernel_version() -> bool {
#[cfg(target_os = "linux")]
{
let info = match uname_release() {
Some(s) => s,
None => return false,
};
parse_kernel_version_ge(&info, 5, 8)
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
#[cfg(any(target_os = "linux", test))]
fn parse_kernel_version_ge(release: &str, req_major: u32, req_minor: u32) -> bool {
let mut parts = release.split(|c: char| !c.is_ascii_digit());
let major = parts.next().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
let minor = parts.next().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
(major, minor) >= (req_major, req_minor)
}
#[cfg(target_os = "linux")]
fn uname_release() -> Option<String> {
use std::ffi::CStr;
unsafe {
let mut info: libc::utsname = std::mem::zeroed();
if libc::uname(&mut info) != 0 {
return None;
}
CStr::from_ptr(info.release.as_ptr()).to_str().ok().map(String::from)
}
}
fn check_btf_available() -> bool {
#[cfg(target_os = "linux")]
{
std::path::Path::new("/sys/kernel/btf/vmlinux").exists()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
fn check_cap_bpf() -> bool {
#[cfg(target_os = "linux")]
{
unsafe { libc::geteuid() == 0 }
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
fn probe_ebpf() -> bool {
check_kernel_version() && check_btf_available() && check_cap_bpf()
}
fn probe_proxy() -> bool {
let supported_platform = cfg!(target_os = "linux") || cfg!(target_os = "macos");
supported_platform && which::which("aa-proxy").is_ok()
}
pub struct LayerDetector;
impl LayerDetector {
pub fn detect() -> LayerSet {
if let Some(layers) = Self::from_env_override() {
return layers;
}
let mut set = LayerSet::SDK;
if probe_ebpf() {
set |= LayerSet::EBPF;
}
if probe_proxy() {
set |= LayerSet::PROXY;
}
set
}
fn from_env_override() -> Option<LayerSet> {
let val = std::env::var("AA_LAYERS").ok()?;
if val.trim().is_empty() {
return None;
}
let mut set = LayerSet::empty();
for token in val.split(',') {
match token.trim().to_lowercase().as_str() {
"ebpf" => set |= LayerSet::EBPF,
"proxy" => set |= LayerSet::PROXY,
"sdk" => set |= LayerSet::SDK,
_ => {} }
}
Some(set)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn individual_flags_have_correct_bits() {
assert_eq!(LayerSet::EBPF.bits(), 0x1);
assert_eq!(LayerSet::PROXY.bits(), 0x2);
assert_eq!(LayerSet::SDK.bits(), 0x4);
}
#[test]
fn flags_combine_with_bitor() {
let set = LayerSet::EBPF | LayerSet::SDK;
assert!(set.contains(LayerSet::EBPF));
assert!(set.contains(LayerSet::SDK));
assert!(!set.contains(LayerSet::PROXY));
}
#[test]
fn names_returns_active_layers_in_order() {
let all = LayerSet::EBPF | LayerSet::PROXY | LayerSet::SDK;
assert_eq!(all.names(), vec!["ebpf", "proxy", "sdk"]);
let sdk_only = LayerSet::SDK;
assert_eq!(sdk_only.names(), vec!["sdk"]);
let proxy_sdk = LayerSet::PROXY | LayerSet::SDK;
assert_eq!(proxy_sdk.names(), vec!["proxy", "sdk"]);
}
#[test]
fn names_empty_for_empty_set() {
let empty = LayerSet::empty();
assert!(empty.names().is_empty());
}
#[test]
fn display_joins_with_plus() {
let all = LayerSet::EBPF | LayerSet::PROXY | LayerSet::SDK;
assert_eq!(format!("{all}"), "ebpf+proxy+sdk");
}
#[test]
fn display_sdk_only() {
assert_eq!(format!("{}", LayerSet::SDK), "sdk");
}
#[test]
fn display_empty_shows_none() {
assert_eq!(format!("{}", LayerSet::empty()), "none");
}
#[test]
fn kernel_version_ge_accepts_exact_match() {
assert!(parse_kernel_version_ge("5.8.0-generic", 5, 8));
}
#[test]
fn kernel_version_ge_accepts_higher() {
assert!(parse_kernel_version_ge("6.1.0", 5, 8));
assert!(parse_kernel_version_ge("5.15.0-91-generic", 5, 8));
}
#[test]
fn kernel_version_ge_rejects_lower() {
assert!(!parse_kernel_version_ge("5.7.19", 5, 8));
assert!(!parse_kernel_version_ge("4.18.0", 5, 8));
}
#[test]
fn kernel_version_ge_handles_garbage() {
assert!(!parse_kernel_version_ge("not-a-version", 5, 8));
}
use std::sync::Mutex;
static ENV_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn detect_always_includes_sdk() {
let _guard = ENV_LOCK.lock().unwrap();
std::env::remove_var("AA_LAYERS");
let set = LayerDetector::detect();
assert!(set.contains(LayerSet::SDK));
}
#[cfg(target_os = "macos")]
#[test]
fn detect_ebpf_false_on_macos() {
let _guard = ENV_LOCK.lock().unwrap();
std::env::remove_var("AA_LAYERS");
let set = LayerDetector::detect();
assert!(!set.contains(LayerSet::EBPF));
}
#[test]
fn aa_layers_override_ebpf_sdk() {
let _guard = ENV_LOCK.lock().unwrap();
std::env::set_var("AA_LAYERS", "ebpf,sdk");
let set = LayerDetector::detect();
assert_eq!(set, LayerSet::EBPF | LayerSet::SDK);
std::env::remove_var("AA_LAYERS");
}
#[test]
fn aa_layers_override_all() {
let _guard = ENV_LOCK.lock().unwrap();
std::env::set_var("AA_LAYERS", "ebpf,proxy,sdk");
let set = LayerDetector::detect();
assert_eq!(set, LayerSet::EBPF | LayerSet::PROXY | LayerSet::SDK);
std::env::remove_var("AA_LAYERS");
}
#[test]
fn aa_layers_override_empty_falls_back_to_detection() {
let _guard = ENV_LOCK.lock().unwrap();
std::env::set_var("AA_LAYERS", "");
let set = LayerDetector::detect();
assert!(set.contains(LayerSet::SDK));
std::env::remove_var("AA_LAYERS");
}
#[test]
fn aa_layers_unknown_tokens_ignored() {
let _guard = ENV_LOCK.lock().unwrap();
std::env::set_var("AA_LAYERS", "sdk,quantum,wasm");
let set = LayerDetector::detect();
assert_eq!(set, LayerSet::SDK);
std::env::remove_var("AA_LAYERS");
}
#[test]
fn aa_layers_case_insensitive() {
let _guard = ENV_LOCK.lock().unwrap();
std::env::set_var("AA_LAYERS", "EBPF,Proxy,SDK");
let set = LayerDetector::detect();
assert_eq!(set, LayerSet::EBPF | LayerSet::PROXY | LayerSet::SDK);
std::env::remove_var("AA_LAYERS");
}
}