From 0737704d4e62bb3065a69d401e88e7bfc12f31c0 Mon Sep 17 00:00:00 2001 From: Mat Wood Date: Tue, 29 Sep 2020 08:32:58 -0700 Subject: fix(sniffer): handle disconnected interfaces (and periodically attempt to re-connect) (#191) * Panic on non-Timeout errors in packet Sniffer * Attempt to Re-establish interface channels after disconnect Sniffer::next() now returns an io::Result so the thread in main can determine between no packets & a sniffing error - Matching on EtherType to remove duplicate code determining IP Version Added Sniffer::reset_channel to allow main to poll a previously connected interface * Error handling & timeout delay performed in Sniffer::next * Removing no longer needed Ether layer in test packet builder --- src/network/sniffer.rs | 26 +++++++++++++++++++++++++- src/os/mod.rs | 2 +- src/os/shared.rs | 2 +- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/network/sniffer.rs b/src/network/sniffer.rs index 52014b8..de587be 100644 --- a/src/network/sniffer.rs +++ b/src/network/sniffer.rs @@ -10,9 +10,15 @@ use ::pnet::packet::udp::UdpPacket; use ::pnet::packet::Packet; use ::ipnetwork::IpNetwork; +use ::std::io::{self, Result}; use ::std::net::{IpAddr, SocketAddr}; +use ::std::thread::park_timeout; use crate::network::{Connection, Protocol}; +use crate::os::shared::get_datalink_channel; + +const PACKET_WAIT_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(10); +const CHANNEL_RESET_DELAY: std::time::Duration = std::time::Duration::from_millis(1000); #[derive(Debug)] pub struct Segment { @@ -96,7 +102,20 @@ impl Sniffer { } } pub fn next(&mut self) -> Option { - let bytes = self.network_frames.next().ok()?; + let bytes = match self.network_frames.next() { + Ok(bytes) => bytes, + Err(err) => match err.kind() { + std::io::ErrorKind::TimedOut => { + park_timeout(PACKET_WAIT_TIMEOUT); + return None; + } + _ => { + park_timeout(CHANNEL_RESET_DELAY); + self.reset_channel().ok(); + return None; + } + }, + }; // See https://github.com/libpnet/libpnet/blob/master/examples/packetdump.rs // VPN interfaces (such as utun0, utun1, etc) have POINT_TO_POINT bit set to 1 let payload_offset = if (self.network_interface.is_loopback() @@ -133,6 +152,11 @@ impl Sniffer { } } } + pub fn reset_channel(&mut self) -> Result<()> { + self.network_frames = get_datalink_channel(&self.network_interface) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "Interface not available"))?; + Ok(()) + } fn handle_v6(ip_packet: Ipv6Packet, network_interface: &NetworkInterface) -> Option { let (protocol, source_port, destination_port, data_length) = extract_transport_protocol!(ip_packet); diff --git a/src/os/mod.rs b/src/os/mod.rs index ec04ecd..4db5b0a 100644 --- a/src/os/mod.rs +++ b/src/os/mod.rs @@ -11,6 +11,6 @@ mod lsof_utils; pub(self) mod windows; mod errors; -mod shared; +pub(crate) mod shared; pub use shared::*; diff --git a/src/os/shared.rs b/src/os/shared.rs index 6731531..3c0fe74 100644 --- a/src/os/shared.rs +++ b/src/os/shared.rs @@ -31,7 +31,7 @@ impl Iterator for TerminalEvents { } } -fn get_datalink_channel( +pub(crate) fn get_datalink_channel( interface: &NetworkInterface, ) -> Result, GetInterfaceErrorKind> { let mut config = Config::default(); -- cgit v1.2.3