use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Instant;
use libwifi::{frame::Frame, Addresses};
use pcap::Capture;
use radiotap::Radiotap;
use crate::debug::{dbg_enabled, dbg_log, pcap_dump};
use crate::eapol::{fallback_data_frame_eapol, handle_data_frame};
use crate::oui::lookup_vendor;
use crate::parse::*;
use crate::types::*;
#[allow(clippy::too_many_arguments)]
pub fn process_packet(
raw: &[u8],
ap_map: &ApMap,
handshake_map: &HandshakeMap,
handshake_count: &HandshakeCount,
handshake_dir: &Path,
capture_handshakes: bool,
probe_map: &ProbeMap,
channel_stats: &ChannelStats,
hs_timeout: u64,
) {
FRAMES_TOTAL.fetch_add(1, Ordering::Relaxed);
pcap_dump(raw);
let rt = match Radiotap::from_bytes(raw) {
Ok(rt) => rt,
Err(_) => return,
};
let signal_dbm = rt.antenna_signal.map(|s| s.value).unwrap_or(-100);
let noise_dbm = rt.antenna_noise.map(|n| n.value);
let freq = rt.channel.map(|c| c.freq).unwrap_or(0);
let rt_channel = if freq > 0 { frequency_to_channel(freq) } else { 0 };
if rt_channel > 0 {
let mut cs = channel_stats.lock().unwrap();
*cs.entry(rt_channel).or_insert(0) += 1;
}
let header_len = rt.header.length;
if header_len >= raw.len() { return; }
let frame_bytes = &raw[header_len..];
if frame_bytes.len() >= 2 {
let fc0 = frame_bytes[0];
let frame_type = (fc0 >> 2) & 0x03;
let subtype = (fc0 >> 4) & 0x0F;
if frame_type == 2 {
FRAMES_DATA.fetch_add(1, Ordering::Relaxed);
}
if dbg_enabled() && frame_type == 2 {
let fc1 = frame_bytes[1];
let protected = (fc1 & 0x40) != 0;
if !protected {
dbg_log(&format!("DATA frame: type={} subtype={} protected=false len={} sig={}dBm ch={}",
frame_type, subtype, frame_bytes.len(), signal_dbm, rt_channel));
}
}
}
let frame = match libwifi::parse_frame(frame_bytes) {
Ok(f) => f,
Err(_) => {
if frame_bytes.len() >= 2 {
let fc0 = frame_bytes[0];
let frame_type = (fc0 >> 2) & 0x03;
if frame_type == 2 && capture_handshakes {
dbg_log(&format!("libwifi parse failed on data frame, trying fallback. FC: {:02x} {:02x}",
frame_bytes[0], frame_bytes[1]));
fallback_data_frame_eapol(raw, frame_bytes, ap_map, handshake_map,
handshake_count, handshake_dir, hs_timeout);
}
}
return;
}
};
let now = Instant::now();
match &frame {
Frame::Beacon(beacon) => {
let bssid = match beacon.header.bssid() {
Some(b) => b.to_string(),
None => return,
};
let base_ie_offset = 24 + 12;
let base_cap_offset = 24 + 10;
let ht_adj: usize = if frame_bytes.len() >= 2 && has_ht_control(frame_bytes[1]) { 4 } else { 0 };
let ie_offset = base_ie_offset + ht_adj;
let ssid = extract_ssid_from_ies(frame_bytes, ie_offset)
.or_else(|| beacon.station_info.ssid.clone())
.unwrap_or_else(|| "<hidden>".to_string());
let channel = extract_channel_from_ies(frame_bytes, ie_offset).unwrap_or(rt_channel);
let ie_info = parse_ies_full(frame_bytes, base_cap_offset);
let mut map = ap_map.lock().unwrap();
let mac = MacAddr::from_str_hex(&bssid).unwrap_or(MacAddr::ZERO);
let ap = map.entry(bssid.clone()).or_insert_with(|| {
let mut a = AccessPoint::new(mac);
a.vendor = lookup_vendor(&bssid);
a
});
ap.signal_dbm = signal_dbm;
ap.noise_dbm = noise_dbm;
ap.last_seen = now;
ap.beacon_count += 1;
ap.push_signal(signal_dbm);
if !ssid.is_empty() && ssid != "<hidden>" {
ap.essid = ssid;
}
if channel > 0 { ap.channel = channel; }
ap.frequency_mhz = freq;
ap.encryption = ie_info.encryption;
ap.wifi_generation = ie_info.wifi_generation;
ap.channel_width = ie_info.channel_width;
ap.bss_color = ie_info.bss_color;
drop(map);
if capture_handshakes {
let mut hs_map = handshake_map.lock().unwrap();
for (key, hs) in hs_map.iter_mut() {
if key.bssid == bssid && hs.beacon_raw.is_none() {
hs.beacon_raw = Some(raw.to_vec());
}
}
}
}
Frame::ProbeResponse(probe) => {
let bssid = match probe.header.bssid() {
Some(b) => b.to_string(),
None => return,
};
let base_ie_offset = 24 + 12;
let base_cap_offset = 24 + 10;
let ht_adj: usize = if frame_bytes.len() >= 2 && has_ht_control(frame_bytes[1]) { 4 } else { 0 };
let ie_offset = base_ie_offset + ht_adj;
let ssid = extract_ssid_from_ies(frame_bytes, ie_offset)
.or_else(|| probe.station_info.ssid.clone())
.unwrap_or_else(|| "<hidden>".to_string());
let channel = extract_channel_from_ies(frame_bytes, ie_offset).unwrap_or(rt_channel);
let ie_info = parse_ies_full(frame_bytes, base_cap_offset);
let mut map = ap_map.lock().unwrap();
let mac = MacAddr::from_str_hex(&bssid).unwrap_or(MacAddr::ZERO);
let ap = map.entry(bssid.clone()).or_insert_with(|| {
let mut a = AccessPoint::new(mac);
a.vendor = lookup_vendor(&bssid);
a
});
ap.signal_dbm = signal_dbm;
ap.noise_dbm = noise_dbm;
ap.last_seen = now;
if !ssid.is_empty() && ssid != "<hidden>" { ap.essid = ssid; }
if channel > 0 { ap.channel = channel; }
ap.frequency_mhz = freq;
ap.encryption = ie_info.encryption;
ap.wifi_generation = ie_info.wifi_generation;
ap.channel_width = ie_info.channel_width;
ap.bss_color = ie_info.bss_color;
}
Frame::ProbeRequest(probe_req) => {
FRAMES_PROBE_REQ.fetch_add(1, Ordering::Relaxed);
if let Some(src) = probe_req.header.src() {
let client_mac = src.to_string();
if client_mac != "ff:ff:ff:ff:ff:ff" {
let ssid = probe_req.station_info.ssid.clone()
.or_else(|| extract_ssid_from_ies(frame_bytes, 24))
.unwrap_or_default();
if !ssid.is_empty() && ssid != "<hidden>" {
let mut pm = probe_map.lock().unwrap();
pm.entry(client_mac.clone())
.or_default()
.insert(ssid.clone());
}
let mut map = ap_map.lock().unwrap();
for ap in map.values_mut() {
if let Some(ci) = ap.clients.get_mut(&client_mac) {
let ssid = probe_req.station_info.ssid.clone().unwrap_or_default();
if !ssid.is_empty() && ssid != "<hidden>"
&& !ci.probed_ssids.contains(&ssid)
{
ci.probed_ssids.push(ssid);
}
}
}
}
}
}
Frame::Data(data) => {
if let Some(bssid_addr) = data.bssid() {
let bssid = bssid_addr.to_string();
let src = data.src().map(|a| a.to_string());
let dst = data.dest().to_string();
handle_data_frame(
raw, frame_bytes, &bssid, src, dst, now, signal_dbm,
ap_map, handshake_map, handshake_count, handshake_dir,
capture_handshakes, hs_timeout,
);
}
}
Frame::QosData(data) => {
if let Some(bssid_addr) = data.bssid() {
let bssid = bssid_addr.to_string();
let src = data.src().map(|a| a.to_string());
let dst = data.dest().to_string();
handle_data_frame(
raw, frame_bytes, &bssid, src, dst, now, signal_dbm,
ap_map, handshake_map, handshake_count, handshake_dir,
capture_handshakes, hs_timeout,
);
}
}
_ => {
if capture_handshakes && frame_bytes.len() >= 26 {
let fc0 = frame_bytes[0];
if (fc0 >> 2) & 0x03 == 2 {
fallback_data_frame_eapol(
raw, frame_bytes, ap_map, handshake_map,
handshake_count, handshake_dir, hs_timeout,
);
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn start_capture(
iface: &str,
ap_map: ApMap,
handshake_map: HandshakeMap,
handshake_count: HandshakeCount,
handshake_dir: PathBuf,
capture_handshakes: bool,
probe_map: ProbeMap,
channel_stats: ChannelStats,
shutdown: Arc<AtomicBool>,
hs_timeout: u64,
) -> Result<(), Box<dyn std::error::Error>> {
let mut cap = Capture::from_device(iface)?
.promisc(true)
.snaplen(65535)
.timeout(100)
.open()?;
std::thread::spawn(move || {
let mut consecutive_errors: u32 = 0;
while !shutdown.load(Ordering::Relaxed) {
match cap.next_packet() {
Ok(packet) => {
consecutive_errors = 0;
process_packet(
packet.data,
&ap_map,
&handshake_map,
&handshake_count,
&handshake_dir,
capture_handshakes,
&probe_map,
&channel_stats,
hs_timeout,
);
}
Err(pcap::Error::TimeoutExpired) => continue,
Err(e) => {
consecutive_errors += 1;
if consecutive_errors <= 3 {
dbg_log(&format!("Capture error: {} (consecutive: {})", e, consecutive_errors));
}
if consecutive_errors > 100 {
dbg_log("Too many consecutive capture errors, stopping");
break;
}
continue;
}
}
}
});
Ok(())
}