use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
#[cfg(target_os = "windows")]
pub const NPCAP_URL: &str = "https://npcap.com/#download";
#[derive(Clone)]
pub struct LoopbackCounters {
pub bytes_recv: Arc<AtomicU64>,
pub bytes_sent: Arc<AtomicU64>,
}
impl LoopbackCounters {
pub fn new() -> Self {
Self {
bytes_recv: Arc::new(AtomicU64::new(0)),
bytes_sent: Arc::new(AtomicU64::new(0)),
}
}
pub fn get_recv(&self) -> u64 {
self.bytes_recv.load(Ordering::Relaxed)
}
pub fn get_sent(&self) -> u64 {
self.bytes_sent.load(Ordering::Relaxed)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LoopbackMode {
None,
Npcap,
}
#[cfg(target_os = "windows")]
pub mod platform {
use super::*;
use std::thread;
#[cfg(feature = "npcap")]
fn is_npcap_installed() -> bool {
extern "system" {
fn LoadLibraryA(name: *const u8) -> *mut std::ffi::c_void;
fn FreeLibrary(handle: *mut std::ffi::c_void) -> i32;
}
unsafe {
let handle = LoadLibraryA(b"wpcap.dll\0".as_ptr());
if handle.is_null() {
false
} else {
FreeLibrary(handle);
true
}
}
}
#[cfg(feature = "npcap")]
pub fn start_npcap(counters: LoopbackCounters) -> Result<String, String> {
if !is_npcap_installed() {
return Err(format!(
"Npcap is not installed on this system.\n\n\
The --npcap flag requires Npcap to capture loopback traffic.\n\
Please install Npcap from: {NPCAP_URL}\n\
(Enable 'Support loopback traffic capture' during installation)\n\n\
Or run without the --npcap flag."
));
}
let devices = pcap::Device::list().map_err(|e| {
format!(
"Failed to list pcap devices: {e}\n\n\
Npcap is not installed or not working.\n\
Please install Npcap from: {NPCAP_URL}\n\
(Enable 'Support loopback traffic capture' during installation)\n\n\
Or try running without --npcap flag, or use --etw instead."
)
})?;
let loopback_dev = devices
.iter()
.find(|d| {
let name_lower = d.name.to_lowercase();
let desc_lower = d.desc.as_deref().unwrap_or("").to_lowercase();
name_lower.contains("loopback")
|| name_lower.contains("npf_loopback")
|| desc_lower.contains("adapter for loopback traffic capture")
|| desc_lower.contains("npcap loopback")
})
.ok_or_else(|| {
let available: Vec<String> = devices
.iter()
.map(|d| {
format!(
" {} ({})",
d.name,
d.desc.as_deref().unwrap_or("no description")
)
})
.collect();
format!(
"Npcap loopback adapter not found.\n\n\
Make sure Npcap is installed with 'Support loopback traffic' enabled.\n\
Download Npcap: {NPCAP_URL}\n\n\
Or try running without --npcap flag, or use --etw instead.\n\n\
Available devices:\n{}",
available.join("\n")
)
})?;
let dev_name = loopback_dev.name.clone();
let info_msg = format!("[npcap] Found loopback device: {dev_name}");
thread::Builder::new()
.name("npcap-loopback".to_string())
.spawn(move || {
if let Err(e) = npcap_capture_loop(&dev_name, &counters) {
eprintln!("[npcap] Capture error: {e}");
}
})
.map_err(|e| format!("Failed to spawn npcap thread: {e}"))?;
Ok(info_msg)
}
#[cfg(feature = "npcap")]
fn npcap_capture_loop(device_name: &str, counters: &LoopbackCounters) -> Result<(), String> {
let mut cap = pcap::Capture::from_device(device_name)
.map_err(|e| format!("Cannot open device: {e}"))?
.promisc(false)
.snaplen(96) .timeout(100) .open()
.map_err(|e| format!("Cannot start capture: {e}"))?;
loop {
match cap.next_packet() {
Ok(packet) => {
let data = packet.data;
if data.len() < 4 {
continue;
}
let ip_payload = &data[4..];
let pkt_len = ip_payload.len() as u64;
if ip_payload.is_empty() {
continue;
}
counters.bytes_recv.fetch_add(pkt_len, Ordering::Relaxed);
counters.bytes_sent.fetch_add(pkt_len, Ordering::Relaxed);
}
Err(pcap::Error::TimeoutExpired) => {
continue;
}
Err(e) => {
return Err(format!("Packet capture error: {e}"));
}
}
}
}
#[cfg(not(feature = "npcap"))]
pub fn start_npcap(_counters: LoopbackCounters) -> Result<String, String> {
Err(format!(
"winload was compiled without Npcap support (feature 'npcap' disabled).\n\
Recompile with: cargo build --features npcap\n\n\
Or download a pre-built release that includes Npcap support.\n\
Npcap download: {NPCAP_URL}"
))
}
}
#[cfg(not(target_os = "windows"))]
pub mod platform {
use super::*;
pub fn start_npcap(_counters: LoopbackCounters) -> Result<String, String> {
Err("--npcap is only supported on Windows. \
On Linux/macOS, loopback traffic is natively available."
.to_string())
}
}