Fix: main thread timeout early than work thread
This commit is contained in:
@@ -14,6 +14,11 @@ use pnet::datalink::{self, Channel, Config as DatalinkConfig};
|
||||
#[cfg(feature = "pcap")]
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[cfg(feature = "pcap")]
|
||||
const OPEN_IFACE_TIMEOUT_MS: u64 = 700;
|
||||
#[cfg(feature = "pcap")]
|
||||
const FRAME_RECV_TIMEOUT_MS: u64 = 200;
|
||||
|
||||
#[cfg(not(feature = "pcap"))]
|
||||
pub async fn capture_events(_options: &LeakWatchOptions) -> Result<Vec<ClassifiedEvent>, DnsLeakError> {
|
||||
Err(DnsLeakError::NotSupported(
|
||||
@@ -24,8 +29,13 @@ pub async fn capture_events(_options: &LeakWatchOptions) -> Result<Vec<Classifie
|
||||
#[cfg(feature = "pcap")]
|
||||
pub async fn capture_events(options: &LeakWatchOptions) -> Result<Vec<ClassifiedEvent>, DnsLeakError> {
|
||||
let options = options.clone();
|
||||
let candidates = format_iface_list(&datalink::interfaces());
|
||||
let timeout_ms = options.duration_ms.saturating_add(2000);
|
||||
let iface_list = datalink::interfaces();
|
||||
let candidates = format_iface_list(&iface_list);
|
||||
let select_budget_ms = (iface_list.len().max(1) as u64).saturating_mul(OPEN_IFACE_TIMEOUT_MS);
|
||||
let timeout_ms = options
|
||||
.duration_ms
|
||||
.saturating_add(select_budget_ms)
|
||||
.saturating_add(2000);
|
||||
let handle = tokio::task::spawn_blocking(move || capture_events_blocking(options));
|
||||
match tokio::time::timeout(Duration::from_millis(timeout_ms), handle).await {
|
||||
Ok(joined) => joined.map_err(|err| DnsLeakError::Io(err.to_string()))?,
|
||||
@@ -88,16 +98,28 @@ fn capture_events_blocking(options: LeakWatchOptions) -> Result<Vec<ClassifiedEv
|
||||
let local_ips = iface.ips.iter().map(|ip| ip.ip()).collect::<Vec<_>>();
|
||||
let iface_name = iface.name.clone();
|
||||
|
||||
let (frame_tx, frame_rx) = mpsc::channel();
|
||||
std::thread::spawn(move || loop {
|
||||
match rx.next() {
|
||||
Ok(frame) => {
|
||||
if frame_tx.send(frame.to_vec()).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_) => continue,
|
||||
}
|
||||
});
|
||||
|
||||
let deadline = Instant::now() + Duration::from_millis(options.duration_ms);
|
||||
let mut events = Vec::new();
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
while Instant::now() < deadline {
|
||||
let frame = match rx.next() {
|
||||
let frame = match frame_rx.recv_timeout(Duration::from_millis(FRAME_RECV_TIMEOUT_MS)) {
|
||||
Ok(frame) => frame,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let ethernet = match EthernetPacket::new(frame) {
|
||||
let ethernet = match EthernetPacket::new(&frame) {
|
||||
Some(packet) => packet,
|
||||
None => continue,
|
||||
};
|
||||
@@ -284,14 +306,8 @@ fn select_interface(
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(iface) = pick_stable_iface(&interfaces) {
|
||||
debug!("dns leak iface pick: stable={}", iface.name);
|
||||
if let Ok(channel) = open_channel_with_timeout(iface, config) {
|
||||
return Ok(channel);
|
||||
}
|
||||
}
|
||||
|
||||
for iface in interfaces.iter() {
|
||||
let ordered = order_interfaces(&interfaces);
|
||||
for iface in ordered.iter() {
|
||||
debug!("dns leak iface pick: try={}", iface.name);
|
||||
if let Ok(channel) = open_channel_with_timeout(iface.clone(), config) {
|
||||
return Ok(channel);
|
||||
@@ -320,7 +336,7 @@ fn open_channel_with_timeout(
|
||||
let _ = tx.send((iface, result));
|
||||
});
|
||||
|
||||
let timeout = Duration::from_millis(700);
|
||||
let timeout = Duration::from_millis(OPEN_IFACE_TIMEOUT_MS);
|
||||
match rx.recv_timeout(timeout) {
|
||||
Ok((iface, Ok(rx))) => Ok((iface, rx)),
|
||||
Ok((_iface, Err(err))) => Err(err),
|
||||
@@ -340,26 +356,27 @@ fn is_named_fallback(name: &str) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(feature = "pcap")]
|
||||
fn pick_stable_iface(
|
||||
fn order_interfaces(
|
||||
interfaces: &[datalink::NetworkInterface],
|
||||
) -> Option<datalink::NetworkInterface> {
|
||||
let mut preferred = interfaces
|
||||
.iter()
|
||||
.filter(|iface| {
|
||||
iface.is_up()
|
||||
&& !iface.is_loopback()
|
||||
&& (is_named_fallback(&iface.name) || !iface.ips.is_empty())
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if preferred.is_empty() {
|
||||
preferred = interfaces
|
||||
.iter()
|
||||
.filter(|iface| !iface.is_loopback())
|
||||
.cloned()
|
||||
.collect();
|
||||
) -> Vec<datalink::NetworkInterface> {
|
||||
let mut preferred = Vec::new();
|
||||
let mut others = Vec::new();
|
||||
for iface in interfaces.iter() {
|
||||
if iface.is_loopback() {
|
||||
continue;
|
||||
}
|
||||
if is_named_fallback(&iface.name) || !iface.ips.is_empty() {
|
||||
preferred.push(iface.clone());
|
||||
} else {
|
||||
others.push(iface.clone());
|
||||
}
|
||||
}
|
||||
preferred.extend(others);
|
||||
if preferred.is_empty() {
|
||||
interfaces.to_vec()
|
||||
} else {
|
||||
preferred
|
||||
}
|
||||
preferred.into_iter().next()
|
||||
}
|
||||
|
||||
#[cfg(feature = "pcap")]
|
||||
|
||||
Reference in New Issue
Block a user