use std::os::raw::c_int;
use crate::protocol::{DiscoveryFrame, GHOSTLINK_ETHERTYPE};
use crate::ring::RingConfig;
pub const MAX_XDP_FRAME_SIZE: usize = 2048;
#[derive(Clone, Debug)]
pub struct XdpConfig {
pub interface_name: String,
pub memory_order: i32,
}
impl Default for XdpConfig {
fn default() -> Self {
Self {
interface_name: "eth0".to_string(),
memory_order: 1, }
}
}
#[derive(Clone, Debug)]
pub struct XdpSocketHandle {
pub fd: c_int,
pub interface_name: String,
}
impl XdpSocketHandle {
pub fn new(interface_name: &str) -> Result<Self, String> {
Err(format!(
"AF_XDP sockets are Linux-only (requested interface: {interface_name})"
))
}
pub fn bind(&self, _interface_name: &str) -> Result<(), String> {
Err("AF_XDP binding requires Linux kernel support".into())
}
pub fn recv_frame(&self, _buffer: &mut [u8]) -> Option<usize> {
None
}
pub fn send_frame(&self, _data: &[u8]) -> Result<(), String> {
Err("AF_XDP send requires specific setup".into())
}
}
#[derive(Debug)]
pub struct XdpFrameReceiver {
config: XdpConfig,
ring_buffer: crate::ring::SpscRingBuffer<Vec<u8>>,
}
impl XdpFrameReceiver {
pub fn new(config: XdpConfig) -> Self {
let ring = crate::ring::SpscRingBuffer::new(RingConfig::default());
Self {
config,
ring_buffer: ring,
}
}
pub fn recv_discovery_frame(&self) -> Option<DiscoveryFrame> {
let _ = (&self.config.interface_name, self.config.memory_order);
None
}
pub fn process_frame(&self, bytes: &[u8]) -> Option<DiscoveryFrame> {
if bytes.len() < 10 {
return None;
}
let ether_type = u16::from_le_bytes([bytes[0], bytes[1]]);
if ether_type != GHOSTLINK_ETHERTYPE {
return None;
}
DiscoveryFrame::decode(bytes).ok()
}
pub fn ring_stats(&self) -> (usize, usize) {
(self.ring_buffer.len(), self.ring_buffer.capacity())
}
}
#[derive(Clone, Debug)]
pub struct EbpfProgramLoader {
program_name: String,
}
impl EbpfProgramLoader {
pub fn new(program_name: &str) -> Self {
Self {
program_name: program_name.to_string(),
}
}
pub fn load(&self, _program_path: &str) -> Result<(), String> {
Err(format!(
"eBPF loading for '{}' requires Linux kernel support",
self.program_name
))
}
pub fn attach(&self, _fd: c_int) -> Result<(), String> {
Err(format!(
"eBPF attachment for '{}' requires Linux kernel support",
self.program_name
))
}
}
#[derive(Clone, Debug, Default)]
pub struct XdpStats {
pub frames_received: u64,
pub frames_dropped: u64,
pub frames_processed: u64,
pub bytes_received: u64,
pub avg_latency_us: f32,
latency_initialized: bool,
}
impl XdpStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_received(&mut self) {
self.frames_received += 1;
}
pub fn record_dropped(&mut self) {
self.frames_received += 1;
self.frames_dropped += 1;
}
pub fn record_processed(&mut self) {
self.frames_processed += 1;
}
pub fn update_latency(&mut self, latency_us: f32) {
if !self.latency_initialized {
self.avg_latency_us = latency_us;
self.latency_initialized = true;
} else {
self.avg_latency_us = self.avg_latency_us * 0.9 + latency_us * 0.1;
}
}
pub fn throughput(&self, duration_seconds: f32) -> Option<f64> {
if duration_seconds > 0.0 {
Some(self.frames_received as f64 / duration_seconds as f64)
} else {
None
}
}
pub fn report(&self) -> String {
format!(
"XDP Statistics\n\
==========\n\
Frames received: {}\n\
Frames dropped: {}\n\
Frames processed: {}\n\
Dropped rate: {:.2}%\n\
Avg latency: {:.2}μs\n",
self.frames_received,
self.frames_dropped,
self.frames_processed,
if self.frames_received > 0 {
(self.frames_dropped as f64 / self.frames_received as f64) * 100.0
} else {
0.0
},
self.avg_latency_us
)
}
}
#[derive(Debug)]
pub struct XdpReceiver {
config: XdpConfig,
frame_receiver: XdpFrameReceiver,
stats: XdpStats,
}
impl XdpReceiver {
pub fn new(config: XdpConfig) -> Self {
let frame_receiver = XdpFrameReceiver::new(config.clone());
Self {
config,
frame_receiver,
stats: XdpStats::new(),
}
}
pub fn recv_loop(&self) -> Result<(), String> {
let _ = (&self.config.interface_name, self.config.memory_order);
Ok(())
}
pub fn process_frame(&mut self, bytes: &[u8]) -> Option<DiscoveryFrame> {
if let Some(frame) = self.frame_receiver.process_frame(bytes) {
self.stats.record_processed();
self.stats.record_received();
Some(frame)
} else {
self.stats.record_dropped();
None
}
}
pub fn stats(&self) -> &XdpStats {
&self.stats
}
}
#[derive(Clone, Debug)]
pub struct XdpSocketManager {
interface_name: String,
fd: Option<c_int>,
}
impl XdpSocketManager {
pub fn new(interface_name: &str) -> Self {
Self {
interface_name: interface_name.to_string(),
fd: None,
}
}
pub fn init(&mut self) -> Result<(), String> {
let _ = &self.interface_name;
Ok(())
}
pub fn recv_frame(&mut self, _buffer: &mut [u8]) -> Option<usize> {
None
}
pub fn send_frame(&mut self, _data: &[u8]) -> Result<(), String> {
Err("AF_XDP send requires specific setup".into())
}
pub fn close(&mut self) {
if let Some(fd) = self.fd.take() {
unsafe {
libc::close(fd);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::{DiscoveryFrame, FrameKind, NodeResources};
#[test]
fn xdp_receiver_processes_frames() {
let mut receiver = XdpReceiver::new(XdpConfig::default());
let node = NodeResources::new("test-node", 24.0, 64.0, "8.9".to_string(), None);
let frame = DiscoveryFrame {
kind: FrameKind::Discovery,
node,
};
let encoded = frame.encode();
let decoded = receiver.process_frame(&encoded);
assert!(decoded.is_some());
}
#[test]
fn xdp_stats_tracks_frames() {
let mut stats = XdpStats::new();
stats.record_received();
stats.record_received();
stats.record_dropped();
stats.record_processed();
assert_eq!(stats.frames_received, 3);
assert_eq!(stats.frames_dropped, 1);
assert_eq!(stats.frames_processed, 1);
}
#[test]
fn xdp_stats_reports_throughput() {
let mut stats = XdpStats::new();
stats.record_received();
stats.record_received();
let throughput = stats.throughput(2.0);
assert_eq!(throughput, Some(1.0));
}
#[test]
fn xdp_stats_updates_latency() {
let mut stats = XdpStats::new();
stats.update_latency(1.0);
assert_eq!(stats.avg_latency_us, 1.0);
stats.update_latency(2.0);
assert!((stats.avg_latency_us - 1.1).abs() < 1e-6);
}
#[test]
fn xdp_receiver_rejects_wrong_ether_type() {
let mut receiver = XdpReceiver::new(XdpConfig::default());
let mut fake_frame = vec![0u8; 10];
fake_frame[0] = 0xB5u8; fake_frame[1] = 0xFF;
let result = receiver.process_frame(&fake_frame);
assert!(result.is_none());
}
#[test]
fn xdp_stats_reports() {
let mut stats = XdpStats::new();
stats.record_received();
stats.record_received();
stats.record_dropped();
stats.update_latency(1.5);
let report = stats.report();
assert!(report.contains("Frames received: 3"));
assert!(report.contains("Frames dropped: 1"));
}
}