use std::collections::HashSet;
use std::sync::{Mutex, OnceLock};
use crate::error::BrainVisionError;
static ALLOWLIST: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
fn allowlist() -> &'static Mutex<HashSet<String>> {
ALLOWLIST.get_or_init(|| Mutex::new(HashSet::new()))
}
pub fn allow_only_endpoint(host: &str, port: u16) -> Result<(), BrainVisionError> {
let key = format!("{host}:{port}");
let mut set = allowlist()
.lock()
.map_err(|_| BrainVisionError::NotSupported("allowlist lock poisoned".into()))?;
set.clear();
set.insert(key);
Ok(())
}
pub(crate) fn endpoint_allowed(host: &str, port: u16) -> bool {
let key = format!("{host}:{port}");
if let Ok(set) = allowlist().lock() {
if set.is_empty() {
true
} else {
set.contains(&key)
}
} else {
false
}
}
#[cfg(target_os = "linux")]
pub fn block_internet() -> Result<(), BrainVisionError> {
use std::sync::atomic::{AtomicBool, Ordering};
static APPLIED: AtomicBool = AtomicBool::new(false);
if APPLIED.load(Ordering::Relaxed) {
return Ok(());
}
let ret = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) };
if ret != 0 {
return Err(BrainVisionError::NotSupported("prctl failed".into()));
}
#[repr(C)]
#[derive(Clone, Copy)]
struct BpfInsn {
code: u16,
jt: u8,
jf: u8,
k: u32,
}
#[repr(C)]
struct BpfProg {
len: u16,
filter: *const BpfInsn,
}
let filter: [BpfInsn; 7] = [
BpfInsn {
code: 0x20,
jt: 0,
jf: 0,
k: 0,
},
BpfInsn {
code: 0x15,
jt: 0,
jf: 3,
k: 41,
},
BpfInsn {
code: 0x20,
jt: 0,
jf: 0,
k: 16,
},
BpfInsn {
code: 0x15,
jt: 2,
jf: 0,
k: 2,
},
BpfInsn {
code: 0x15,
jt: 1,
jf: 0,
k: 10,
},
BpfInsn {
code: 0x06,
jt: 0,
jf: 0,
k: 0x7fff_0000,
},
BpfInsn {
code: 0x06,
jt: 0,
jf: 0,
k: 0x0005_0001,
},
];
let prog = BpfProg {
len: 7,
filter: filter.as_ptr(),
};
let ret = unsafe { libc::syscall(libc::SYS_seccomp, 1u64, 0u64, &prog as *const _) };
if ret != 0 {
return Err(BrainVisionError::NotSupported(format!(
"seccomp failed: {}",
std::io::Error::last_os_error()
)));
}
APPLIED.store(true, Ordering::Relaxed);
Ok(())
}
#[cfg(not(target_os = "linux"))]
pub fn block_internet() -> Result<(), BrainVisionError> {
Ok(())
}
pub fn is_sandboxed() -> bool {
if let Ok(set) = allowlist().lock() {
!set.is_empty()
} else {
false
}
}