use crate::error::Error;
use crate::packet::Packet;
use crate::pcap::CaptureWriter;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum TapErrorPolicy {
#[default]
Continue,
DropTap,
FailStream,
}
pub(crate) trait TapWriter: Send {
fn write(&mut self, pkt: &Packet<'_>, snaplen: Option<u32>)
-> Result<(), pcap_file::PcapError>;
fn write_raw(
&mut self,
data: &[u8],
ts: crate::packet::Timestamp,
original_len: usize,
snaplen: Option<u32>,
) -> Result<(), pcap_file::PcapError>;
}
impl<W: std::io::Write + Send + 'static> TapWriter for CaptureWriter<W> {
fn write(
&mut self,
pkt: &Packet<'_>,
snaplen: Option<u32>,
) -> Result<(), pcap_file::PcapError> {
match snaplen {
Some(cap) => self.write_packet_truncated(pkt, cap as usize),
None => self.write_packet(pkt),
}
}
fn write_raw(
&mut self,
data: &[u8],
ts: crate::packet::Timestamp,
original_len: usize,
snaplen: Option<u32>,
) -> Result<(), pcap_file::PcapError> {
self.write_raw(data, ts, original_len, snaplen)
}
}
impl<W: std::io::Write + Send + 'static> TapWriter for crate::pcap::CaptureWriterNg<W> {
fn write(
&mut self,
pkt: &Packet<'_>,
snaplen: Option<u32>,
) -> Result<(), pcap_file::PcapError> {
match snaplen {
Some(cap) => self.write_packet_truncated(pkt, cap as usize),
None => self.write_packet(pkt),
}
}
fn write_raw(
&mut self,
data: &[u8],
ts: crate::packet::Timestamp,
original_len: usize,
snaplen: Option<u32>,
) -> Result<(), pcap_file::PcapError> {
self.write_raw(data, ts, original_len, snaplen)
}
}
pub struct PcapTap {
inner: Box<dyn TapWriter>,
policy: TapErrorPolicy,
snaplen: Option<u32>,
dropped: bool,
}
impl PcapTap {
pub(crate) fn new<W>(writer: CaptureWriter<W>, policy: TapErrorPolicy) -> Self
where
W: std::io::Write + Send + 'static,
{
Self::from_writer(Box::new(writer), policy)
}
pub(crate) fn from_writer(writer: Box<dyn TapWriter>, policy: TapErrorPolicy) -> Self {
Self {
inner: writer,
policy,
snaplen: None,
dropped: false,
}
}
pub(crate) fn set_snaplen(&mut self, cap: u32) {
self.snaplen = Some(cap);
}
pub(crate) fn write_or_handle(&mut self, pkt: &Packet<'_>) -> Option<Error> {
if self.dropped {
return None;
}
let result = self.inner.write(pkt, self.snaplen);
self.handle_result(result)
}
pub(crate) fn write_raw_or_handle(
&mut self,
data: &[u8],
ts: crate::packet::Timestamp,
original_len: usize,
) -> Option<Error> {
if self.dropped {
return None;
}
let result = self.inner.write_raw(data, ts, original_len, self.snaplen);
self.handle_result(result)
}
fn handle_result(&mut self, result: Result<(), pcap_file::PcapError>) -> Option<Error> {
match result {
Ok(()) => None,
Err(err) => match self.policy {
TapErrorPolicy::Continue => {
tracing::warn!(
target: "netring::pcap_tap",
?err,
"pcap tap write failed; continuing (policy = Continue)"
);
None
}
TapErrorPolicy::DropTap => {
tracing::warn!(
target: "netring::pcap_tap",
?err,
"pcap tap write failed; dropping tap (policy = DropTap)"
);
self.dropped = true;
None
}
TapErrorPolicy::FailStream => Some(Error::Io(std::io::Error::other(format!(
"pcap tap write failed: {err}"
)))),
},
}
}
pub fn is_dropped(&self) -> bool {
self.dropped
}
pub fn snaplen(&self) -> Option<u32> {
self.snaplen
}
}
#[cfg(test)]
mod tests {
use super::*;
struct NoopWriter;
impl TapWriter for NoopWriter {
fn write(
&mut self,
_pkt: &Packet<'_>,
_snaplen: Option<u32>,
) -> Result<(), pcap_file::PcapError> {
Ok(())
}
fn write_raw(
&mut self,
_data: &[u8],
_ts: crate::packet::Timestamp,
_original_len: usize,
_snaplen: Option<u32>,
) -> Result<(), pcap_file::PcapError> {
Ok(())
}
}
fn tap_with(policy: TapErrorPolicy) -> PcapTap {
PcapTap::from_writer(Box::new(NoopWriter), policy)
}
fn simulated_failure() -> Result<(), pcap_file::PcapError> {
Err(pcap_file::PcapError::IoError(std::io::Error::other(
"simulated",
)))
}
#[test]
fn continue_policy_swallows_errors() {
let mut tap = tap_with(TapErrorPolicy::Continue);
for _ in 0..3 {
assert!(tap.handle_result(simulated_failure()).is_none());
}
assert!(!tap.is_dropped(), "Continue keeps the tap alive");
}
#[test]
fn drop_policy_retires_tap_after_first_failure() {
let mut tap = tap_with(TapErrorPolicy::DropTap);
assert!(tap.handle_result(simulated_failure()).is_none());
assert!(tap.is_dropped());
}
#[test]
fn fail_stream_policy_surfaces_error() {
let mut tap = tap_with(TapErrorPolicy::FailStream);
let err = tap
.handle_result(simulated_failure())
.expect("FailStream propagates");
assert!(matches!(err, Error::Io(_)));
}
#[test]
fn ok_path_no_error_no_drop() {
let mut tap = tap_with(TapErrorPolicy::FailStream);
for _ in 0..5 {
assert!(tap.handle_result(Ok(())).is_none());
}
assert!(!tap.is_dropped());
}
#[test]
fn default_policy_is_continue() {
assert_eq!(TapErrorPolicy::default(), TapErrorPolicy::Continue);
}
}