use std::marker::PhantomData;
use std::net::IpAddr;
use flowscope::L4Proto;
use super::predicate::{Atom, Glob, Predicate};
use crate::config::ipnet::IpNet;
use crate::protocol::{FlowProtocol, MessageProtocol};
#[derive(Debug, Clone, Copy)]
pub struct PacketTier;
#[derive(Debug, Clone, Copy)]
pub struct FlowTier<P: FlowProtocol>(PhantomData<fn() -> P>);
#[derive(Debug, Clone, Copy)]
pub struct SessionTier<P: MessageProtocol>(PhantomData<fn() -> P>);
#[derive(Debug, Clone)]
#[must_use = "a SubscriptionBuilder does nothing until `.to(handler)` (or `.into_predicate()`)"]
pub struct SubscriptionBuilder<T> {
predicate: Predicate,
_tier: PhantomData<fn() -> T>,
}
impl<T> SubscriptionBuilder<T> {
fn start() -> Self {
Self {
predicate: Predicate::Always,
_tier: PhantomData,
}
}
fn and_atom(mut self, atom: Atom) -> Self {
self.predicate = self.predicate.and(Predicate::Atom(atom));
self
}
pub fn into_predicate(self) -> Predicate {
self.predicate
}
pub fn expr(mut self, filter: &str) -> Result<Self, super::expr::ParseError> {
let parsed = super::expr::parse(filter)?;
self.predicate = self.predicate.and(parsed);
Ok(self)
}
}
pub fn packet() -> SubscriptionBuilder<PacketTier> {
SubscriptionBuilder::start()
}
pub fn flow<P: FlowProtocol>() -> SubscriptionBuilder<FlowTier<P>> {
SubscriptionBuilder::start()
}
pub fn session<P: MessageProtocol>() -> SubscriptionBuilder<SessionTier<P>> {
SubscriptionBuilder::start()
}
impl<T> SubscriptionBuilder<T> {
pub fn tcp(self) -> Self {
self.and_atom(Atom::Proto(L4Proto::Tcp))
}
pub fn udp(self) -> Self {
self.and_atom(Atom::Proto(L4Proto::Udp))
}
pub fn icmp(self) -> Self {
self.and_atom(Atom::Proto(L4Proto::Icmp))
}
pub fn proto(self, proto: L4Proto) -> Self {
self.and_atom(Atom::Proto(proto))
}
pub fn src_port(self, port: u16) -> Self {
self.and_atom(Atom::SrcPort(port))
}
pub fn dst_port(self, port: u16) -> Self {
self.and_atom(Atom::DstPort(port))
}
pub fn port(self, port: u16) -> Self {
self.and_atom(Atom::AnyPort(port))
}
pub fn src_host(self, ip: IpAddr) -> Self {
self.and_atom(Atom::SrcHost(ip))
}
pub fn dst_host(self, ip: IpAddr) -> Self {
self.and_atom(Atom::DstHost(ip))
}
pub fn host(self, ip: IpAddr) -> Self {
self.and_atom(Atom::AnyHost(ip))
}
pub fn src_net(self, net: IpNet) -> Self {
self.and_atom(Atom::SrcNet(net))
}
pub fn dst_net(self, net: IpNet) -> Self {
self.and_atom(Atom::DstNet(net))
}
pub fn net(self, net: IpNet) -> Self {
self.and_atom(Atom::AnyNet(net))
}
pub fn vlan(self, id: u16) -> Self {
self.and_atom(Atom::VlanId(id))
}
pub fn ethertype(self, ty: u16) -> Self {
self.and_atom(Atom::EtherType(ty))
}
pub fn arp(self) -> Self {
self.ethertype(0x0806)
}
}
impl SubscriptionBuilder<PacketTier> {
pub fn to<H>(self, handler: H) -> super::packet::PacketSubscription
where
H: for<'a, 'c> Fn(
&flowscope::PacketView<'a>,
&mut crate::ctx::Ctx<'c>,
) -> crate::error::Result<()>
+ Send
+ Sync
+ 'static,
{
super::packet::PacketSubscription {
predicate: std::sync::Arc::new(arc_swap::ArcSwap::from_pointee(self.predicate)),
handler: std::sync::Arc::new(handler),
}
}
}
impl<P: FlowProtocol> SubscriptionBuilder<FlowTier<P>> {
pub fn bytes_over(self, n: u64) -> Self {
self.and_atom(Atom::BytesOver(n))
}
pub fn packets_over(self, n: u64) -> Self {
self.and_atom(Atom::PacketsOver(n))
}
pub fn to<H>(self, handler: H) -> super::flow::FlowSubscription<P>
where
H: for<'c> Fn(
&crate::protocol::event_typed::FlowEnded<P>,
&mut crate::ctx::Ctx<'c>,
) -> crate::error::Result<()>
+ Send
+ Sync
+ 'static,
{
super::flow::FlowSubscription {
predicate: self.predicate,
handler: std::sync::Arc::new(handler),
}
}
}
mod sealed {
pub trait Sealed {}
}
pub trait HasSni: MessageProtocol + sealed::Sealed {}
#[cfg(feature = "tls")]
impl sealed::Sealed for crate::protocol::builtin::Tls {}
#[cfg(feature = "tls")]
impl HasSni for crate::protocol::builtin::Tls {}
#[cfg(feature = "tls")]
impl sealed::Sealed for crate::protocol::builtin::TlsHandshake {}
#[cfg(feature = "tls")]
impl HasSni for crate::protocol::builtin::TlsHandshake {}
pub trait HasHttpHost: MessageProtocol + sealed::Sealed {}
#[cfg(feature = "http")]
impl sealed::Sealed for crate::protocol::builtin::Http {}
#[cfg(feature = "http")]
impl HasHttpHost for crate::protocol::builtin::Http {}
pub trait HasQname: MessageProtocol + sealed::Sealed {}
#[cfg(feature = "dns")]
impl sealed::Sealed for crate::protocol::builtin::Dns {}
#[cfg(feature = "dns")]
impl HasQname for crate::protocol::builtin::Dns {}
impl<P: HasSni> SubscriptionBuilder<SessionTier<P>> {
pub fn sni_glob(self, pattern: impl Into<String>) -> Self {
self.and_atom(Atom::SniGlob(Glob::new(pattern)))
}
}
impl<P: HasHttpHost> SubscriptionBuilder<SessionTier<P>> {
pub fn host_glob(self, pattern: impl Into<String>) -> Self {
self.and_atom(Atom::HttpHostGlob(Glob::new(pattern)))
}
}
impl<P: HasQname> SubscriptionBuilder<SessionTier<P>> {
pub fn qname_glob(self, pattern: impl Into<String>) -> Self {
self.and_atom(Atom::DnsQnameGlob(Glob::new(pattern)))
}
}
impl<P> SubscriptionBuilder<SessionTier<P>>
where
P: MessageProtocol,
P::Message: super::session::L7Fields,
{
pub fn to<H>(self, handler: H) -> super::session::SessionSubscription<P>
where
H: for<'c> Fn(
&<P as crate::protocol::Protocol>::Message,
&mut crate::ctx::Ctx<'c>,
) -> crate::error::Result<()>
+ Send
+ Sync
+ 'static,
{
super::session::SessionSubscription {
predicate: self.predicate,
handler: std::sync::Arc::new(handler),
}
}
}
#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
use super::*;
use crate::protocol::builtin::{Tcp, Udp};
#[test]
fn packet_tier_builds_kernel_pushable_conjunction() {
let p = packet().tcp().dst_port(443).into_predicate();
match p {
Predicate::And(l, r) => {
assert_eq!(*l, Predicate::Atom(Atom::Proto(L4Proto::Tcp)));
assert_eq!(*r, Predicate::Atom(Atom::DstPort(443)));
}
other => panic!("expected And, got {other:?}"),
}
}
#[test]
fn empty_builder_is_always() {
assert_eq!(packet().into_predicate(), Predicate::Always);
assert_eq!(flow::<Tcp>().into_predicate(), Predicate::Always);
}
#[test]
fn flow_tier_count_filters() {
let p = flow::<Udp>().udp().bytes_over(1024).into_predicate();
match p {
Predicate::And(_, r) => assert_eq!(*r, Predicate::Atom(Atom::BytesOver(1024))),
other => panic!("expected And, got {other:?}"),
}
}
#[cfg(all(feature = "tls", feature = "http", feature = "dns"))]
#[test]
fn session_tier_l7_globs() {
use crate::protocol::builtin::{Dns, Http, Tls};
let tls = session::<Tls>().tcp().sni_glob("*.bank").into_predicate();
match tls {
Predicate::And(_, r) => {
assert_eq!(*r, Predicate::Atom(Atom::SniGlob(Glob::new("*.bank"))))
}
other => panic!("expected And, got {other:?}"),
}
let dns = session::<Dns>().qname_glob("*.evil.test").into_predicate();
assert_eq!(
dns,
Predicate::Atom(Atom::DnsQnameGlob(Glob::new("*.evil.test")))
);
let http = session::<Http>().host_glob("api.*").into_predicate();
assert_eq!(
http,
Predicate::Atom(Atom::HttpHostGlob(Glob::new("api.*")))
);
}
#[test]
fn net_and_host_combinators() {
let net: IpNet = "10.0.0.0/8".parse().unwrap();
let p = packet()
.src_net(net)
.dst_host(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)))
.into_predicate();
let mut atoms = Vec::new();
collect_atoms(&p, &mut atoms);
assert!(atoms.contains(&Atom::SrcNet(net)));
assert!(atoms.contains(&Atom::DstHost(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)))));
}
fn collect_atoms(p: &Predicate, out: &mut Vec<Atom>) {
match p {
Predicate::Atom(a) => out.push(a.clone()),
Predicate::And(l, r) | Predicate::Or(l, r) => {
collect_atoms(l, out);
collect_atoms(r, out);
}
Predicate::Not(inner) => collect_atoms(inner, out),
Predicate::Always => {}
}
}
}