use crate::error::ManagerError;
use crate::xdp_prog::{XdpLoader, XdpProgramName};
use std::sync::atomic::{AtomicU32, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BankId {
A,
B,
}
impl BankId {
pub fn as_u32(&self) -> u32 {
match self {
BankId::A => 0,
BankId::B => 1,
}
}
pub fn from_u32(id: u32) -> Option<Self> {
match id {
0 => Some(BankId::A),
1 => Some(BankId::B),
_ => None,
}
}
pub fn other(&self) -> BankId {
match self {
BankId::A => BankId::B,
BankId::B => BankId::A,
}
}
}
impl std::fmt::Display for BankId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BankId::A => write!(f, "BankA"),
BankId::B => write!(f, "BankB"),
}
}
}
struct BankState {
id: BankId,
loader: Option<XdpLoader>,
user_version: u64,
is_active: bool,
}
impl std::fmt::Debug for BankState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BankState")
.field("id", &self.id)
.field("user_version", &self.user_version)
.field("is_active", &self.is_active)
.field("has_loader", &self.loader.is_some())
.finish()
}
}
pub struct DualBankManager {
active_bank: BankState,
standby_bank: BankState,
global_version: AtomicU32,
switch_count: AtomicU32,
}
impl DualBankManager {
pub fn new() -> Result<Self, ManagerError> {
let bank_a_loader = match XdpLoader::load(XdpProgramName::Main) {
Ok(loader) => {
tracing::info!("Bank A loaded successfully (xdp_main)");
Some(loader)
}
Err(e) => {
tracing::warn!(error = %e, "Failed to load Bank A");
None
}
};
let bank_b_loader = match XdpLoader::load(XdpProgramName::Main) {
Ok(loader) => {
tracing::info!("Bank B loaded successfully (xdp_main)");
Some(loader)
}
Err(e) => {
tracing::warn!(error = %e, "Failed to load Bank B");
None
}
};
Ok(Self {
active_bank: BankState {
id: BankId::A,
loader: bank_a_loader,
user_version: 1,
is_active: true,
},
standby_bank: BankState {
id: BankId::B,
loader: bank_b_loader,
user_version: 1,
is_active: false,
},
global_version: AtomicU32::new(1),
switch_count: AtomicU32::new(0),
})
}
pub fn new_strict() -> Result<Self, ManagerError> {
let bank_a_loader =
XdpLoader::load(XdpProgramName::Main).map_err(|e| {
ManagerError::Load(format!("Bank A (xdp_main) load failed: {e}"))
})?;
let bank_b_loader = XdpLoader::load(XdpProgramName::Main).map_err(|e| {
ManagerError::Load(format!("Bank B (xdp_main) load failed: {e}"))
})?;
Ok(Self {
active_bank: BankState {
id: BankId::A,
loader: Some(bank_a_loader),
user_version: 1,
is_active: true,
},
standby_bank: BankState {
id: BankId::B,
loader: Some(bank_b_loader),
user_version: 1,
is_active: false,
},
global_version: AtomicU32::new(1),
switch_count: AtomicU32::new(0),
})
}
#[inline]
pub fn is_ebpf_fully_loaded(&self) -> bool {
self.active_bank.loader.is_some() && self.standby_bank.loader.is_some()
}
#[inline]
pub fn is_ebpf_active_bank_loaded(&self) -> bool {
self.active_bank.loader.is_some()
}
#[inline]
pub fn ebpf_loaded_banks(&self) -> (bool, bool) {
(
matches!(self.active_bank.id, BankId::A) && self.active_bank.loader.is_some()
|| matches!(self.standby_bank.id, BankId::A) && self.standby_bank.loader.is_some(),
matches!(self.active_bank.id, BankId::B) && self.active_bank.loader.is_some()
|| matches!(self.standby_bank.id, BankId::B) && self.standby_bank.loader.is_some(),
)
}
pub fn switch(&mut self, ifindex: i32) -> Result<BankId, ManagerError> {
if self.active_bank.loader.is_none() || self.standby_bank.loader.is_none() {
return Err(ManagerError::InvalidState);
}
let active_prog = self
.active_bank
.loader
.as_ref()
.map(XdpLoader::program_name);
if active_prog != Some(XdpProgramName::Main) {
return Err(ManagerError::NotXdpMain(format!(
"活动 Bank ({}) 程序为 {:?},非 xdp_main(完整过滤链被绕过),拒绝切换",
self.active_bank.id, active_prog
)));
}
let has_link = self
.active_bank
.loader
.as_ref()
.is_some_and(|l| l.active_link().is_some());
if has_link {
let active = self
.active_bank
.loader
.as_mut()
.ok_or(ManagerError::InvalidState)?;
let standby = self
.standby_bank
.loader
.as_mut()
.ok_or(ManagerError::InvalidState)?;
active.update_link_prog(standby).map_err(|e| {
ManagerError::SwitchFailed(format!(
"bpf_link 原子替换失败(旧程序仍在线,未切换): {e}"
))
})?;
} else {
tracing::debug!(
ifindex,
"活动 Bank 未挂载 bpf_link,仅执行用户态 Bank 切换"
);
}
let new_active_id = self.standby_bank.id;
let old_active = std::mem::replace(
&mut self.active_bank,
BankState {
id: self.standby_bank.id,
loader: self.standby_bank.loader.take(),
user_version: self.standby_bank.user_version,
is_active: true,
},
);
self.standby_bank = BankState {
id: old_active.id,
loader: old_active.loader,
user_version: old_active.user_version,
is_active: false,
};
let link = self
.standby_bank
.loader
.as_mut()
.and_then(XdpLoader::take_link);
if link.is_some()
&& let Some(active) = self.active_bank.loader.as_mut() {
active.set_link(link);
} else if link.is_none() {
tracing::warn!(
ifindex,
"bpf_link 转移失败:备用 Bank 无可转移的 link(take_link 返回 None),\
下次 switch 将无法对同一 Link 执行 update_prog"
);
}
self.switch_count.fetch_add(1, Ordering::SeqCst);
Ok(new_active_id)
}
pub fn active_bank_id(&self) -> BankId {
self.active_bank.id
}
pub fn standby_bank_id(&self) -> BankId {
self.standby_bank.id
}
pub fn active_user_version(&self) -> u64 {
self.active_bank.user_version
}
pub fn standby_user_version(&self) -> u64 {
self.standby_bank.user_version
}
pub fn switch_count(&self) -> u32 {
self.switch_count.load(Ordering::SeqCst)
}
pub fn global_version(&self) -> u32 {
self.global_version.load(Ordering::SeqCst)
}
pub fn increment_version(&self) -> u32 {
self.global_version.fetch_add(1, Ordering::SeqCst) + 1
}
pub fn active_loader(&self) -> Option<&XdpLoader> {
self.active_bank.loader.as_ref()
}
pub fn active_loader_mut(&mut self) -> Option<&mut XdpLoader> {
self.active_bank.loader.as_mut()
}
pub fn standby_loader(&self) -> Option<&XdpLoader> {
self.standby_bank.loader.as_ref()
}
pub fn standby_loader_mut(&mut self) -> Option<&mut XdpLoader> {
self.standby_bank.loader.as_mut()
}
}
impl std::fmt::Debug for DualBankManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DualBankManager")
.field("active", &self.active_bank)
.field("standby", &self.standby_bank)
.field("switch_count", &self.switch_count)
.field("global_version", &self.global_version)
.finish()
}
}
impl Drop for DualBankManager {
fn drop(&mut self) {
tracing::debug!("DualBankManager dropped");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bank_id_values() {
assert_eq!(BankId::A.as_u32(), 0);
assert_eq!(BankId::B.as_u32(), 1);
assert_eq!(BankId::from_u32(0), Some(BankId::A));
assert_eq!(BankId::from_u32(1), Some(BankId::B));
assert_eq!(BankId::from_u32(2), None);
}
#[test]
fn test_bank_id_other() {
assert_eq!(BankId::A.other(), BankId::B);
assert_eq!(BankId::B.other(), BankId::A);
}
#[test]
fn test_bank_id_display() {
assert_eq!(format!("{}", BankId::A), "BankA");
assert_eq!(format!("{}", BankId::B), "BankB");
}
#[test]
fn test_dual_bank_manager_creation() {
let mgr = match DualBankManager::new() {
Ok(mgr) => mgr,
Err(e) => {
eprintln!("SKIP: DualBankManager::new() 返回 Err: {e}");
return;
}
};
assert_eq!(mgr.active_bank_id(), BankId::A);
assert_eq!(mgr.standby_bank_id(), BankId::B);
assert_eq!(mgr.switch_count(), 0);
assert_eq!(mgr.global_version(), 1);
if mgr.is_ebpf_fully_loaded() {
let (a_loaded, b_loaded) = mgr.ebpf_loaded_banks();
assert!(a_loaded, "is_ebpf_fully_loaded 为真时 Bank A loader 必须存在");
assert!(b_loaded, "is_ebpf_fully_loaded 为真时 Bank B loader 必须存在");
assert!(mgr.active_loader().is_some());
assert!(mgr.standby_loader().is_some());
assert_eq!(mgr.active_user_version(), 1);
assert_eq!(mgr.standby_user_version(), 1);
} else {
eprintln!(
"SKIP: eBPF 字节码加载失败(权限/内核不支持),loaded banks = {:?},仅校验用户态状态",
mgr.ebpf_loaded_banks()
);
}
}
#[test]
fn test_dual_bank_manager_versioning() {
let result = DualBankManager::new();
if let Ok(mgr) = result {
let v1 = mgr.global_version();
let v2 = mgr.increment_version();
assert!(v2 > v1);
}
}
#[test]
fn test_bank_id_debug() {
let debug_str = format!("{:?}", BankId::A);
assert!(!debug_str.is_empty());
let debug_str = format!("{:?}", BankId::B);
assert!(!debug_str.is_empty());
}
#[test]
fn test_bank_id_clone_copy() {
let a = BankId::A;
let b = a;
assert_eq!(a, b);
assert_eq!(a.as_u32(), b.as_u32());
}
#[test]
fn test_bank_id_unique() {
assert_ne!(BankId::A, BankId::B);
assert_eq!(BankId::A, BankId::A);
assert_eq!(BankId::B, BankId::B);
}
#[test]
fn test_bank_id_eq() {
assert_eq!(BankId::A, BankId::A);
assert_eq!(BankId::B, BankId::B);
assert_ne!(BankId::A, BankId::B);
}
#[test]
fn test_bank_id_from_u32_boundary() {
assert_eq!(BankId::from_u32(0), Some(BankId::A));
assert_eq!(BankId::from_u32(1), Some(BankId::B));
assert_eq!(BankId::from_u32(u32::MAX), None);
assert_eq!(BankId::from_u32(2), None);
assert_eq!(BankId::from_u32(100), None);
}
#[test]
fn test_bank_id_other_involution() {
assert_eq!(BankId::A.other().other(), BankId::A);
assert_eq!(BankId::B.other().other(), BankId::B);
}
#[test]
fn test_dual_bank_manager_debug() {
if let Ok(mgr) = DualBankManager::new() {
let debug_str = format!("{:?}", mgr);
assert!(!debug_str.is_empty());
assert!(debug_str.contains("DualBankManager"));
}
}
#[test]
fn test_dual_bank_manager_initial_versions() {
if let Ok(mgr) = DualBankManager::new() {
assert_eq!(mgr.active_user_version(), 1);
assert_eq!(mgr.standby_user_version(), 1);
assert_eq!(mgr.global_version(), 1);
}
}
#[test]
fn test_dual_bank_manager_increment_version_multiple() {
if let Ok(mgr) = DualBankManager::new() {
let v1 = mgr.global_version();
let v2 = mgr.increment_version();
let v3 = mgr.increment_version();
let v4 = mgr.increment_version();
assert_eq!(v2, v1 + 1);
assert_eq!(v3, v2 + 1);
assert_eq!(v4, v3 + 1);
}
}
#[test]
fn test_dual_bank_manager_bank_ids_consistent() {
if let Ok(mgr) = DualBankManager::new() {
let active = mgr.active_bank_id();
let standby = mgr.standby_bank_id();
assert_ne!(active, standby);
assert_eq!(active.other(), standby);
assert_eq!(standby.other(), active);
}
}
#[test]
fn test_manager_error_display() {
use crate::error::ManagerError;
let e = ManagerError::InvalidState;
assert!(format!("{}", e).contains("Invalid bank state"));
let e = ManagerError::Load("test".to_string());
assert!(format!("{}", e).contains("Load error"));
}
#[test]
fn test_bank_active_program_is_xdp_main() {
let mgr = match DualBankManager::new() {
Ok(mgr) => mgr,
Err(e) => {
eprintln!("SKIP: DualBankManager::new() 返回 Err: {e}");
return;
}
};
if mgr.is_ebpf_fully_loaded() {
let active = mgr
.active_loader()
.expect("双轨加载成功时活动 loader 必须存在");
assert_eq!(
active.program_name(),
XdpProgramName::Main,
"NET-016:默认活动 Bank 必须为 xdp_main"
);
assert!(
active.is_full_filter_chain(),
"NET-016:活动程序必须是完整过滤链 xdp_main"
);
} else {
eprintln!(
"SKIP: eBPF 未真实加载({}/{}),无法校验活动程序为 xdp_main",
mgr.ebpf_loaded_banks().0,
mgr.ebpf_loaded_banks().1
);
}
}
}