use std::collections::VecDeque;
use std::pin::Pin;
use std::task::{Context, Poll};
use flowscope::FlowExtractor;
use flowscope::driver_unified::{Driver, DriverBuilder};
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>> + Send>>;
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: Send + 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)
}
}
#[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, ProtocolMessage> = Driver::builder(extractor);
let mut slots: usize = 0;
#[cfg(feature = "http")]
if let Some(ports) = self.http_ports {
builder = builder.session_on_ports(
flowscope::http::HttpParser::default(),
ports,
ProtocolMessage::Http,
);
slots += 1;
}
#[cfg(feature = "dns")]
if let Some(ports) = self.dns_udp_ports {
builder = builder.datagram_on_ports(
flowscope::dns::DnsUdpParser::with_correlation(),
ports,
ProtocolMessage::Dns,
);
slots += 1;
}
#[cfg(feature = "dns")]
if let Some(ports) = self.dns_tcp_ports {
builder = builder.session_on_ports(
flowscope::dns::DnsTcpParser::default(),
ports,
ProtocolMessage::Dns,
);
slots += 1;
}
#[cfg(feature = "tls")]
if let Some(ports) = self.tls_ports {
builder = builder.session_on_ports(
flowscope::tls::TlsParser::default(),
ports,
ProtocolMessage::Tls,
);
slots += 1;
}
#[cfg(feature = "tls")]
if let Some(ports) = self.tls_handshake_ports {
builder = builder.session_on_ports(
flowscope::tls::TlsHandshakeParser::default(),
ports,
ProtocolMessage::TlsHandshake,
);
slots += 1;
}
#[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(),
};
builder = builder.datagram_broadcast(parser, ProtocolMessage::Icmp);
slots += 1;
}
#[cfg(feature = "http")]
if self.http_heuristic {
builder = builder.session_heuristic(
flowscope::http::HttpParser::default(),
flowscope::detect::signatures::http_request,
ProtocolMessage::Http,
);
slots += 1;
}
#[cfg(feature = "tls")]
if self.tls_handshake_heuristic {
builder = builder.session_heuristic(
flowscope::tls::TlsHandshakeParser::default(),
flowscope::detect::signatures::tls_client_hello,
ProtocolMessage::TlsHandshake,
);
slots += 1;
}
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,
pending: VecDeque::new(),
});
Ok(ProtocolMonitor { inner, slots })
}
}
struct DriverDrivenStream<S, E>
where
S: PacketSource + std::os::fd::AsRawFd,
E: FlowExtractor,
{
packet_stream: PacketStream<S>,
driver: Driver<E, ProtocolMessage>,
pending: VecDeque<ProtocolEvent<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.pending.extend(this.driver.track(view));
}
}
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
Poll::Ready(None) => return Poll::Ready(None),
Poll::Pending => return Poll::Pending,
}
}
}
}
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]));
}
}
}