#![allow(deprecated)]
use std::collections::VecDeque;
use std::pin::Pin;
use std::task::{Context, Poll};
use flowscope::FlowExtractor;
use flowscope::driver::{Driver, DriverBuilder, Event as FsEvent, SlotHandle, SlotMessage};
use futures_core::Stream;
use super::event::{ProtocolEvent, ProtocolMessage};
use crate::async_adapters::tokio_adapter::PacketStream;
use crate::error::Error;
use crate::traits::PacketSource;
use crate::{AsyncCapture, Capture};
type BoxedEventStream<K> = Pin<Box<dyn Stream<Item = Result<ProtocolEvent<K>, Error>>>>;
#[deprecated(
since = "0.21.0",
note = "Use `netring::monitor::Monitor::builder()` (the 0.20 typed-handler API) instead. \
ProtocolMonitor is scheduled for removal in 0.22.0. See docs/MIGRATING_0.20_TO_0.21.md."
)]
pub struct ProtocolMonitor<K> {
inner: BoxedEventStream<K>,
slots: usize,
}
impl<K> std::fmt::Debug for ProtocolMonitor<K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProtocolMonitor")
.field("source_count", &1usize)
.field("slots", &self.slots)
.finish()
}
}
impl<K> ProtocolMonitor<K> {
pub fn source_count(&self) -> usize {
1
}
pub fn alive_sources(&self) -> usize {
1
}
pub fn slot_count(&self) -> usize {
self.slots
}
}
impl<K> Stream for ProtocolMonitor<K>
where
K: Unpin + 'static,
{
type Item = Result<ProtocolEvent<K>, Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.inner.as_mut().poll_next(cx)
}
}
#[deprecated(
since = "0.21.0",
note = "Use `netring::monitor::Monitor::builder()` (the 0.20 typed-handler API) instead. \
Removed in 0.22.0. See docs/MIGRATING_0.20_TO_0.21.md."
)]
#[derive(Debug, Default)]
pub struct ProtocolMonitorBuilder {
interface: Option<String>,
enable_flow: bool,
#[cfg(feature = "http")]
http_ports: Option<Vec<u16>>,
#[cfg(feature = "dns")]
dns_udp_ports: Option<Vec<u16>>,
#[cfg(feature = "dns")]
dns_tcp_ports: Option<Vec<u16>>,
#[cfg(feature = "tls")]
tls_ports: Option<Vec<u16>>,
#[cfg(feature = "tls")]
tls_handshake_ports: Option<Vec<u16>>,
#[cfg(feature = "icmp")]
enable_icmp: Option<IcmpScope>,
#[cfg(feature = "http")]
http_heuristic: bool,
#[cfg(feature = "tls")]
tls_handshake_heuristic: bool,
}
#[cfg(feature = "icmp")]
#[derive(Debug, Clone, Copy)]
enum IcmpScope {
Both,
V4,
V6,
}
impl ProtocolMonitorBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn interface(mut self, name: impl Into<String>) -> Self {
self.interface = Some(name.into());
self
}
pub fn flow(mut self) -> Self {
self.enable_flow = true;
self
}
#[cfg(feature = "http")]
pub fn http(self) -> Self {
self.http_on_ports([80, 8080])
}
#[cfg(feature = "http")]
pub fn http_on_ports(mut self, ports: impl IntoIterator<Item = u16>) -> Self {
self.http_ports = Some(ports.into_iter().collect());
self
}
#[cfg(feature = "dns")]
pub fn dns(self) -> Self {
self.dns_udp_on_ports([53])
}
#[cfg(feature = "dns")]
pub fn dns_udp_on_ports(mut self, ports: impl IntoIterator<Item = u16>) -> Self {
self.dns_udp_ports = Some(ports.into_iter().collect());
self
}
#[cfg(feature = "dns")]
pub fn dns_tcp_on_ports(mut self, ports: impl IntoIterator<Item = u16>) -> Self {
self.dns_tcp_ports = Some(ports.into_iter().collect());
self
}
#[cfg(feature = "tls")]
pub fn tls(self) -> Self {
self.tls_on_ports([443, 8443])
}
#[cfg(feature = "tls")]
pub fn tls_on_ports(mut self, ports: impl IntoIterator<Item = u16>) -> Self {
self.tls_ports = Some(ports.into_iter().collect());
self
}
#[cfg(feature = "tls")]
pub fn tls_handshake(self) -> Self {
self.tls_handshake_on_ports([443, 8443])
}
#[cfg(feature = "tls")]
pub fn tls_handshake_on_ports(mut self, ports: impl IntoIterator<Item = u16>) -> Self {
self.tls_handshake_ports = Some(ports.into_iter().collect());
self
}
#[cfg(feature = "http")]
pub fn http_heuristic(mut self) -> Self {
self.http_heuristic = true;
self
}
#[cfg(feature = "tls")]
pub fn tls_handshake_heuristic(mut self) -> Self {
self.tls_handshake_heuristic = true;
self
}
#[cfg(feature = "icmp")]
pub fn icmp(mut self) -> Self {
self.enable_icmp = Some(IcmpScope::Both);
self
}
#[cfg(feature = "icmp")]
pub fn icmp_v4_only(mut self) -> Self {
self.enable_icmp = Some(IcmpScope::V4);
self
}
#[cfg(feature = "icmp")]
pub fn icmp_v6_only(mut self) -> Self {
self.enable_icmp = Some(IcmpScope::V6);
self
}
pub fn build<E>(self, extractor: E) -> Result<ProtocolMonitor<E::Key>, Error>
where
E: FlowExtractor + Unpin + Clone + Send + 'static,
E::Key: Eq + std::hash::Hash + Clone + Send + Sync + Unpin + 'static,
{
let iface = self.interface.ok_or_else(|| {
Error::Config("ProtocolMonitorBuilder: .interface(...) is required".into())
})?;
let mut builder: DriverBuilder<E> = Driver::builder(extractor);
let mut slots: Vec<Box<dyn ProtocolSlot<E::Key>>> = Vec::new();
#[cfg(feature = "http")]
if let Some(ports) = self.http_ports {
let handle = builder.session_on_ports(flowscope::http::HttpParser::default(), ports);
slots.push(Box::new(TypedSlot::new(handle, ProtocolMessage::Http)));
}
#[cfg(feature = "dns")]
if let Some(ports) = self.dns_udp_ports {
let handle =
builder.datagram_on_ports(flowscope::dns::DnsUdpParser::with_correlation(), ports);
slots.push(Box::new(TypedSlot::new(handle, ProtocolMessage::Dns)));
}
#[cfg(feature = "dns")]
if let Some(ports) = self.dns_tcp_ports {
let handle = builder.session_on_ports(flowscope::dns::DnsTcpParser::default(), ports);
slots.push(Box::new(TypedSlot::new(handle, ProtocolMessage::Dns)));
}
#[cfg(feature = "tls")]
if let Some(ports) = self.tls_ports {
let handle = builder.session_on_ports(flowscope::tls::TlsParser::default(), ports);
slots.push(Box::new(TypedSlot::new(handle, ProtocolMessage::Tls)));
}
#[cfg(feature = "tls")]
if let Some(ports) = self.tls_handshake_ports {
let handle =
builder.session_on_ports(flowscope::tls::TlsHandshakeParser::default(), ports);
slots.push(Box::new(TypedSlot::new(
handle,
ProtocolMessage::TlsHandshake,
)));
}
#[cfg(feature = "icmp")]
if let Some(scope) = self.enable_icmp {
let parser = match scope {
IcmpScope::Both => flowscope::icmp::IcmpParser::new(),
IcmpScope::V4 => flowscope::icmp::IcmpParser::new().v4_only(),
IcmpScope::V6 => flowscope::icmp::IcmpParser::new().v6_only(),
};
let handle = builder.datagram_broadcast(parser);
slots.push(Box::new(TypedSlot::new(handle, ProtocolMessage::Icmp)));
}
#[cfg(feature = "http")]
if self.http_heuristic {
let handle = builder.session_heuristic(
flowscope::http::HttpParser::default(),
flowscope::detect::signatures::http_request,
);
slots.push(Box::new(TypedSlot::new(handle, ProtocolMessage::Http)));
}
#[cfg(feature = "tls")]
if self.tls_handshake_heuristic {
let handle = builder.session_heuristic(
flowscope::tls::TlsHandshakeParser::default(),
flowscope::detect::signatures::tls_client_hello,
);
slots.push(Box::new(TypedSlot::new(
handle,
ProtocolMessage::TlsHandshake,
)));
}
let slot_count = slots.len();
let driver = builder.build();
let cap = AsyncCapture::open(&iface)?;
let packet_stream = cap.into_stream();
let inner: BoxedEventStream<E::Key> = Box::pin(DriverDrivenStream {
packet_stream,
driver,
slots,
pending: VecDeque::new(),
lifecycle_buf: Vec::with_capacity(64),
});
Ok(ProtocolMonitor {
inner,
slots: slot_count,
})
}
}
trait ProtocolSlot<K> {
fn drain_into(&mut self, out: &mut VecDeque<ProtocolEvent<K>>);
}
struct TypedSlot<M, K>
where
M: Send + 'static,
K: Send + 'static,
{
handle: SlotHandle<M, K>,
lift: fn(M) -> ProtocolMessage,
parser_kind: &'static str,
scratch: Vec<SlotMessage<M, K>>,
}
impl<M, K> TypedSlot<M, K>
where
M: Send + 'static,
K: Send + 'static,
{
fn new(handle: SlotHandle<M, K>, lift: fn(M) -> ProtocolMessage) -> Self {
let parser_kind = handle.parser_kind();
Self {
handle,
lift,
parser_kind,
scratch: Vec::new(),
}
}
}
impl<M, K> ProtocolSlot<K> for TypedSlot<M, K>
where
M: Send + 'static,
K: Send + Clone + 'static,
{
fn drain_into(&mut self, out: &mut VecDeque<ProtocolEvent<K>>) {
self.scratch.clear();
let n = self.handle.drain(&mut self.scratch);
if n == 0 {
return;
}
let lift = self.lift;
let parser_kind = self.parser_kind;
for slot_msg in self.scratch.drain(..) {
out.push_back(ProtocolEvent::Message {
key: slot_msg.key,
side: slot_msg.side,
parser_kind,
message: lift(slot_msg.message),
ts: slot_msg.ts,
});
}
}
}
struct DriverDrivenStream<S, E>
where
S: PacketSource + std::os::fd::AsRawFd,
E: FlowExtractor,
E::Key: 'static,
{
packet_stream: PacketStream<S>,
driver: Driver<E>,
slots: Vec<Box<dyn ProtocolSlot<E::Key>>>,
pending: VecDeque<ProtocolEvent<E::Key>>,
lifecycle_buf: Vec<FsEvent<E::Key>>,
}
impl<S, E> Stream for DriverDrivenStream<S, E>
where
S: PacketSource + std::os::fd::AsRawFd + Unpin + Send + 'static,
E: FlowExtractor + Clone + Unpin + Send + 'static,
E::Key: Eq + std::hash::Hash + Clone + Send + Sync + Unpin + 'static,
{
type Item = Result<ProtocolEvent<E::Key>, Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
loop {
if let Some(ev) = self.pending.pop_front() {
return Poll::Ready(Some(Ok(ev)));
}
let this = self.as_mut().get_mut();
match Pin::new(&mut this.packet_stream).poll_next(cx) {
Poll::Ready(Some(Ok(batch))) => {
for owned in batch {
let view = flowscope::PacketView::new(&owned.data, owned.timestamp);
this.lifecycle_buf.clear();
this.driver.track_into(view, &mut this.lifecycle_buf);
for fs_evt in this.lifecycle_buf.drain(..) {
if let Some(ev) = translate_lifecycle(fs_evt) {
this.pending.push_back(ev);
}
}
for slot in &mut this.slots {
slot.drain_into(&mut this.pending);
}
}
}
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
Poll::Ready(None) => return Poll::Ready(None),
Poll::Pending => return Poll::Pending,
}
}
}
}
fn translate_lifecycle<K>(evt: FsEvent<K>) -> Option<ProtocolEvent<K>> {
Some(match evt {
FsEvent::FlowStarted { key, ts, l4 } => ProtocolEvent::FlowStarted { key, ts, l4 },
FsEvent::FlowEstablished { key, ts, l4 } => ProtocolEvent::FlowEstablished { key, ts, l4 },
FsEvent::FlowPacket {
key,
side,
len,
ts,
tcp,
} => ProtocolEvent::FlowPacket {
key,
side,
len,
ts,
tcp,
},
FsEvent::FlowEnded {
key,
reason,
stats,
history,
l4,
ts,
} => ProtocolEvent::FlowEnded {
key,
reason,
stats,
history,
l4,
ts,
},
FsEvent::FlowTick { key, stats, ts } => ProtocolEvent::FlowTick { key, stats, ts },
FsEvent::ParserClosed {
key,
parser_kind,
reason,
ts,
} => ProtocolEvent::ParserClosed {
key,
parser_kind,
reason,
ts,
},
FsEvent::FlowAnomaly { key, kind, ts } => ProtocolEvent::FlowAnomaly { key, kind, ts },
FsEvent::TrackerAnomaly { kind, ts } => ProtocolEvent::TrackerAnomaly { kind, ts },
_ => return None,
})
}
type _NetringCapture = Capture;
#[cfg(test)]
mod tests {
use super::*;
use flowscope::extract::FiveTuple;
#[test]
fn builder_requires_interface() {
let result = ProtocolMonitorBuilder::new()
.flow()
.build(FiveTuple::bidirectional());
match result {
Err(Error::Config(msg)) => assert!(
msg.contains(".interface"),
"unexpected error message: {msg}"
),
Err(other) => panic!("expected Config, got {other:?}"),
Ok(_) => panic!("expected error, got Ok"),
}
}
#[test]
fn builder_default_has_no_protocols_enabled() {
let b = ProtocolMonitorBuilder::new().interface("lo");
assert!(!b.enable_flow);
#[cfg(feature = "http")]
assert!(b.http_ports.is_none());
#[cfg(feature = "dns")]
{
assert!(b.dns_udp_ports.is_none());
assert!(b.dns_tcp_ports.is_none());
}
#[cfg(feature = "tls")]
{
assert!(b.tls_ports.is_none());
assert!(b.tls_handshake_ports.is_none());
}
}
#[test]
fn builder_setters_record_state() {
let b = ProtocolMonitorBuilder::new().interface("eth0").flow();
assert_eq!(b.interface.as_deref(), Some("eth0"));
assert!(b.enable_flow);
#[cfg(feature = "http")]
{
let b = ProtocolMonitorBuilder::new().http_on_ports([8080, 8443]);
assert_eq!(b.http_ports, Some(vec![8080, 8443]));
}
#[cfg(feature = "dns")]
{
let b = ProtocolMonitorBuilder::new().dns();
assert_eq!(b.dns_udp_ports, Some(vec![53]));
}
}
}