1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
14
15use etherparse::{NetSlice, SlicedPacket, TransportSlice};
16use thiserror::Error;
17
18use rustc_hash::FxHashSet;
19
20use crate::flow::{FlowKey, normalize_ip};
21
22#[derive(Debug, Error)]
26pub enum FilterError {
27 #[error("invalid IP address or CIDR: '{0}'")]
28 InvalidIp(String),
29
30 #[error("invalid port or range (expected e.g. '443' or '1024-65535'): '{0}'")]
31 InvalidPort(String),
32
33 #[error("invalid protocol (expected 'tcp', 'udp', 'icmp', 'icmp6', or a number 0-255): '{0}'")]
34 InvalidProto(String),
35
36 #[error("invalid flow ID (expected hex, optionally prefixed with '0x'): '{0}'")]
37 InvalidFlowId(String),
38
39 #[error("invalid datetime (expected RFC 3339 or Unix epoch integer in seconds): '{0}'")]
40 InvalidDateTime(String),
41
42 #[error(
43 "invalid TCP flags (e.g. 'SYN', 'SYN+ACK', 'FIN:exact'); \
44 known flags: FIN SYN RST PSH ACK URG ECE CWR: '{0}'"
45 )]
46 InvalidTcpFlags(String),
47
48 #[error("invalid filter operator (expected 'and', 'or', or 'not'): '{0}'")]
49 InvalidOp(String),
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum IpNet {
60 V4 { addr: Ipv4Addr, prefix_len: u8 },
61 V6 { addr: Ipv6Addr, prefix_len: u8 },
62}
63
64impl IpNet {
65 pub fn parse(s: &str) -> Result<Self, FilterError> {
70 let err = || FilterError::InvalidIp(s.to_owned());
71
72 if let Some((ip_str, prefix_str)) = s.split_once('/') {
73 let prefix_len: u8 = prefix_str.parse().map_err(|_| err())?;
74 let ip = normalize_ip(ip_str.parse::<IpAddr>().map_err(|_| err())?);
75 match ip {
76 IpAddr::V4(v4) => {
77 if prefix_len > 32 {
78 return Err(err());
79 }
80 Ok(Self::V4 {
81 addr: v4,
82 prefix_len,
83 })
84 }
85 IpAddr::V6(v6) => {
86 if prefix_len > 128 {
87 return Err(err());
88 }
89 Ok(Self::V6 {
90 addr: v6,
91 prefix_len,
92 })
93 }
94 }
95 } else {
96 match normalize_ip(s.parse::<IpAddr>().map_err(|_| err())?) {
97 IpAddr::V4(v4) => Ok(Self::V4 {
98 addr: v4,
99 prefix_len: 32,
100 }),
101 IpAddr::V6(v6) => Ok(Self::V6 {
102 addr: v6,
103 prefix_len: 128,
104 }),
105 }
106 }
107 }
108
109 pub fn contains(&self, ip: IpAddr) -> bool {
115 let ip = normalize_ip(ip);
116 match (self, ip) {
117 (Self::V4 { addr, prefix_len }, IpAddr::V4(v4)) => {
118 if *prefix_len == 0 {
119 return true;
120 }
121 let shift = 32 - u32::from(*prefix_len);
122 (u32::from(*addr) >> shift) == (u32::from(v4) >> shift)
123 }
124 (Self::V6 { addr, prefix_len }, IpAddr::V6(v6)) => {
125 if *prefix_len == 0 {
126 return true;
127 }
128 let shift = 128 - u128::from(*prefix_len);
129 (u128::from(*addr) >> shift) == (u128::from(v6) >> shift)
130 }
131 _ => false,
132 }
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub struct PortRange {
141 pub start: u16,
142 pub end: u16,
143}
144
145impl PortRange {
146 pub fn parse(s: &str) -> Result<Self, FilterError> {
152 let err = || FilterError::InvalidPort(s.to_owned());
153 if let Some((lo, hi)) = s.split_once('-') {
154 let start: u16 = lo.trim().parse().map_err(|_| err())?;
155 let end: u16 = hi.trim().parse().map_err(|_| err())?;
156 if start > end {
157 return Err(err());
158 }
159 Ok(Self { start, end })
160 } else {
161 let p: u16 = s.trim().parse().map_err(|_| err())?;
162 Ok(Self { start: p, end: p })
163 }
164 }
165
166 pub fn contains(self, port: u16) -> bool {
168 port >= self.start && port <= self.end
169 }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub struct TcpFlagsFilter {
185 pub mask: u8,
187 pub value: u8,
189 pub exact: bool,
191}
192
193impl TcpFlagsFilter {
194 pub fn parse(s: &str) -> Result<Self, FilterError> {
206 let err = || FilterError::InvalidTcpFlags(s.to_owned());
207
208 let (flags_part, exact) = if let Some(f) = s.strip_suffix(":exact") {
209 (f, true)
210 } else if let Some(f) = s.strip_suffix(":any") {
211 (f, false)
212 } else {
213 (s, false)
214 };
215
216 let mut mask = 0u8;
217 for token in flags_part.split('+') {
218 let bit = match token.trim().to_ascii_uppercase().as_str() {
219 "FIN" => 0x01,
220 "SYN" => 0x02,
221 "RST" => 0x04,
222 "PSH" => 0x08,
223 "ACK" => 0x10,
224 "URG" => 0x20,
225 "ECE" => 0x40,
226 "CWR" => 0x80,
227 _ => return Err(err()),
228 };
229 mask |= bit;
230 }
231 if mask == 0 {
232 return Err(err());
233 }
234 Ok(Self {
235 mask,
236 value: mask,
237 exact,
238 })
239 }
240
241 pub fn matches(self, flags: u8) -> bool {
243 if self.exact {
244 flags == self.value
246 } else {
247 (flags & self.mask) != 0
248 }
249 }
250}
251
252pub fn parse_proto_list(s: &str) -> Result<Vec<u8>, FilterError> {
262 s.split(',').map(|p| parse_proto(p.trim())).collect()
263}
264
265fn parse_proto(s: &str) -> Result<u8, FilterError> {
266 match s.to_ascii_lowercase().as_str() {
267 "tcp" => Ok(6),
268 "udp" => Ok(17),
269 "icmp" => Ok(1),
270 "icmp6" | "icmpv6" => Ok(58),
271 "sctp" => Ok(132),
272 "esp" => Ok(50),
273 "ah" => Ok(51),
274 other => other
275 .parse::<u8>()
276 .map_err(|_| FilterError::InvalidProto(s.to_owned())),
277 }
278}
279
280pub fn parse_flow_ids(s: &str) -> Result<Vec<u64>, FilterError> {
287 s.split(',')
288 .map(|id| {
289 let id = id.trim().trim_start_matches("0x");
290 u64::from_str_radix(id, 16).map_err(|_| FilterError::InvalidFlowId(id.to_owned()))
291 })
292 .collect()
293}
294
295pub fn parse_datetime_ns(s: &str) -> Result<u64, FilterError> {
306 if let Ok(secs) = s.parse::<u64>() {
308 return Ok(secs.saturating_mul(1_000_000_000));
309 }
310 chrono::DateTime::parse_from_rfc3339(s)
311 .map_err(|_| FilterError::InvalidDateTime(s.to_owned()))
312 .and_then(|dt| {
313 let ns = dt
314 .timestamp_nanos_opt()
315 .ok_or_else(|| FilterError::InvalidDateTime(s.to_owned()))?;
316 Ok(ns.max(0) as u64)
317 })
318}
319
320#[derive(Debug, Clone)]
327pub struct PacketMeta {
328 pub timestamp_ns: u64,
330 pub captured_len: u32,
332 pub flow_key: Option<FlowKey>,
334 pub tcp_flags: u8,
336}
337
338impl PacketMeta {
339 pub fn from_packet(timestamp_ns: u64, captured_len: u32, data: &[u8]) -> Self {
341 let (flow_key, tcp_flags) = parse_ethernet(data);
342 Self {
343 timestamp_ns,
344 captured_len,
345 flow_key,
346 tcp_flags,
347 }
348 }
349}
350
351fn parse_ethernet(data: &[u8]) -> (Option<FlowKey>, u8) {
352 let sliced = match SlicedPacket::from_ethernet(data) {
353 Ok(s) => s,
354 Err(_) => return (None, 0),
355 };
356
357 let (src_ip, dst_ip, protocol) = match &sliced.net {
358 Some(NetSlice::Ipv4(v4)) => {
359 let h = v4.header();
360 (
361 IpAddr::V4(h.source_addr()),
362 IpAddr::V4(h.destination_addr()),
363 h.protocol().0,
364 )
365 }
366 Some(NetSlice::Ipv6(v6)) => {
367 let h = v6.header();
368 (
369 IpAddr::V6(h.source_addr()),
370 IpAddr::V6(h.destination_addr()),
371 h.next_header().0,
372 )
373 }
374 _ => return (None, 0),
375 };
376
377 let (src_port, dst_port, tcp_flags) = match &sliced.transport {
378 Some(TransportSlice::Tcp(tcp)) => {
379 let mut f = 0u8;
380 if tcp.fin() {
381 f |= 0x01;
382 }
383 if tcp.syn() {
384 f |= 0x02;
385 }
386 if tcp.rst() {
387 f |= 0x04;
388 }
389 if tcp.psh() {
390 f |= 0x08;
391 }
392 if tcp.ack() {
393 f |= 0x10;
394 }
395 if tcp.urg() {
396 f |= 0x20;
397 }
398 if tcp.ece() {
399 f |= 0x40;
400 }
401 if tcp.cwr() {
402 f |= 0x80;
403 }
404 (tcp.source_port(), tcp.destination_port(), f)
405 }
406 Some(TransportSlice::Udp(udp)) => (udp.source_port(), udp.destination_port(), 0),
407 _ => (0, 0, 0),
408 };
409
410 let key = FlowKey::new(src_ip, dst_ip, src_port, dst_port, protocol);
411 (Some(key), tcp_flags)
412}
413
414#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
418pub enum Op {
419 #[default]
421 And,
422 Or,
424 Not,
426}
427
428impl Op {
429 pub fn parse(s: &str) -> Result<Self, FilterError> {
434 match s.to_ascii_lowercase().as_str() {
435 "and" => Ok(Self::And),
436 "or" => Ok(Self::Or),
437 "not" => Ok(Self::Not),
438 _ => Err(FilterError::InvalidOp(s.to_owned())),
439 }
440 }
441}
442
443trait FilterBody {
448 fn protocols(&self) -> &[u8];
449 fn src_ips(&self) -> &[IpNet];
450 fn dst_ips(&self) -> &[IpNet];
451 fn ips(&self) -> &[IpNet];
452 fn src_ports(&self) -> &[PortRange];
453 fn dst_ports(&self) -> &[PortRange];
454 fn ports(&self) -> &[PortRange];
455 fn flow_ids(&self) -> &FxHashSet<u64>;
456 fn start_ns(&self) -> Option<u64>;
457 fn end_ns(&self) -> Option<u64>;
458 fn tcp_flags(&self) -> Option<TcpFlagsFilter>;
459 fn min_len(&self) -> Option<u32>;
460 fn max_len(&self) -> Option<u32>;
461 fn unidirectional(&self) -> bool;
462}
463
464macro_rules! impl_filter_body {
466 ($T:ty) => {
467 impl FilterBody for $T {
468 fn protocols(&self) -> &[u8] {
469 &self.protocols
470 }
471 fn src_ips(&self) -> &[IpNet] {
472 &self.src_ips
473 }
474 fn dst_ips(&self) -> &[IpNet] {
475 &self.dst_ips
476 }
477 fn ips(&self) -> &[IpNet] {
478 &self.ips
479 }
480 fn src_ports(&self) -> &[PortRange] {
481 &self.src_ports
482 }
483 fn dst_ports(&self) -> &[PortRange] {
484 &self.dst_ports
485 }
486 fn ports(&self) -> &[PortRange] {
487 &self.ports
488 }
489 fn flow_ids(&self) -> &FxHashSet<u64> {
490 &self.flow_ids
491 }
492 fn start_ns(&self) -> Option<u64> {
493 self.from_ns
494 }
495 fn end_ns(&self) -> Option<u64> {
496 self.to_ns
497 }
498 fn tcp_flags(&self) -> Option<TcpFlagsFilter> {
499 self.tcp_flags
500 }
501 fn min_len(&self) -> Option<u32> {
502 self.min_len
503 }
504 fn max_len(&self) -> Option<u32> {
505 self.max_len
506 }
507 fn unidirectional(&self) -> bool {
508 self.unidirectional
509 }
510 }
511 };
512}
513
514fn eval_body(b: &impl FilterBody, meta: &PacketMeta) -> bool {
520 if let Some(from) = b.start_ns()
522 && meta.timestamp_ns < from
523 {
524 return false;
525 }
526 if let Some(to) = b.end_ns()
527 && meta.timestamp_ns > to
528 {
529 return false;
530 }
531
532 if let Some(min) = b.min_len()
534 && meta.captured_len < min
535 {
536 return false;
537 }
538 if let Some(max) = b.max_len()
539 && meta.captured_len > max
540 {
541 return false;
542 }
543
544 let need_flow = !b.protocols().is_empty()
546 || !b.src_ips().is_empty()
547 || !b.dst_ips().is_empty()
548 || !b.ips().is_empty()
549 || !b.src_ports().is_empty()
550 || !b.dst_ports().is_empty()
551 || !b.ports().is_empty()
552 || !b.flow_ids().is_empty()
553 || b.tcp_flags().is_some();
554
555 if !need_flow {
556 return true;
557 }
558
559 let key = match &meta.flow_key {
561 Some(k) => k,
562 None => return false,
563 };
564
565 if !b.protocols().is_empty() && !b.protocols().contains(&key.protocol) {
567 return false;
568 }
569
570 if !b.src_ips().is_empty() && !b.src_ips().iter().any(|n| n.contains(key.src_ip)) {
572 return false;
573 }
574 if !b.dst_ips().is_empty() && !b.dst_ips().iter().any(|n| n.contains(key.dst_ip)) {
575 return false;
576 }
577 if !b.ips().is_empty()
578 && !b
579 .ips()
580 .iter()
581 .any(|n| n.contains(key.src_ip) || n.contains(key.dst_ip))
582 {
583 return false;
584 }
585
586 if matches!(key.protocol, 6 | 17) {
588 if !b.src_ports().is_empty() && !b.src_ports().iter().any(|r| r.contains(key.src_port)) {
589 return false;
590 }
591 if !b.dst_ports().is_empty() && !b.dst_ports().iter().any(|r| r.contains(key.dst_port)) {
592 return false;
593 }
594 if !b.ports().is_empty()
595 && !b
596 .ports()
597 .iter()
598 .any(|r| r.contains(key.src_port) || r.contains(key.dst_port))
599 {
600 return false;
601 }
602 }
603
604 if !b.flow_ids().is_empty() {
606 let id = key.flow_id(b.unidirectional());
607 if !b.flow_ids().contains(&id) {
608 return false;
609 }
610 }
611
612 if let Some(ff) = b.tcp_flags()
614 && key.protocol == 6
615 && !ff.matches(meta.tcp_flags)
616 {
617 return false;
618 }
619
620 true
621}
622
623#[derive(Debug, Default, Clone)]
650pub struct Filter {
651 pub negate: bool,
653
654 pub rules: Vec<FilterRule>,
656
657 pub protocols: Vec<u8>,
659
660 pub src_ips: Vec<IpNet>,
662 pub dst_ips: Vec<IpNet>,
664 pub ips: Vec<IpNet>,
666
667 pub src_ports: Vec<PortRange>,
669 pub dst_ports: Vec<PortRange>,
671 pub ports: Vec<PortRange>,
673
674 pub flow_ids: FxHashSet<u64>,
676
677 pub from_ns: Option<u64>,
679 pub to_ns: Option<u64>,
681
682 pub tcp_flags: Option<TcpFlagsFilter>,
684
685 pub min_len: Option<u32>,
687 pub max_len: Option<u32>,
689
690 pub unidirectional: bool,
692}
693
694impl_filter_body!(Filter);
695
696impl Filter {
697 pub fn is_empty(&self) -> bool {
701 !self.negate
702 && self.rules.is_empty()
703 && self.protocols.is_empty()
704 && self.src_ips.is_empty()
705 && self.dst_ips.is_empty()
706 && self.ips.is_empty()
707 && self.src_ports.is_empty()
708 && self.dst_ports.is_empty()
709 && self.ports.is_empty()
710 && self.flow_ids.is_empty()
711 && self.from_ns.is_none()
712 && self.to_ns.is_none()
713 && self.tcp_flags.is_none()
714 && self.min_len.is_none()
715 && self.max_len.is_none()
716 }
717
718 pub fn matches(&self, meta: &PacketMeta) -> bool {
726 let base = eval_body(self, meta);
727 let acc = if self.negate { !base } else { base };
728 self.rules.iter().fold(acc, |acc, rule| {
729 let rule_result = eval_body(rule, meta);
730 match rule.op {
731 Op::And => acc && rule_result,
732 Op::Or => acc || rule_result,
733 Op::Not => acc && !rule_result,
734 }
735 })
736 }
737}
738
739#[derive(Debug, Default, Clone)]
747pub struct FilterRule {
748 pub op: Op,
750
751 pub protocols: Vec<u8>,
752 pub src_ips: Vec<IpNet>,
753 pub dst_ips: Vec<IpNet>,
754 pub ips: Vec<IpNet>,
755 pub src_ports: Vec<PortRange>,
756 pub dst_ports: Vec<PortRange>,
757 pub ports: Vec<PortRange>,
758 pub flow_ids: FxHashSet<u64>,
759 pub from_ns: Option<u64>,
760 pub to_ns: Option<u64>,
761 pub tcp_flags: Option<TcpFlagsFilter>,
762 pub min_len: Option<u32>,
763 pub max_len: Option<u32>,
764 pub unidirectional: bool,
765}
766
767impl_filter_body!(FilterRule);
768
769#[cfg(test)]
772mod tests {
773 use super::*;
774 use std::net::Ipv4Addr;
775
776 fn v4(a: u8, b: u8, c: u8, d: u8) -> IpAddr {
777 IpAddr::V4(Ipv4Addr::new(a, b, c, d))
778 }
779
780 fn meta(
781 ts_ns: u64,
782 caplen: u32,
783 src: IpAddr,
784 dst: IpAddr,
785 sport: u16,
786 dport: u16,
787 proto: u8,
788 tcp_flags: u8,
789 ) -> PacketMeta {
790 PacketMeta {
791 timestamp_ns: ts_ns,
792 captured_len: caplen,
793 flow_key: Some(FlowKey::new(src, dst, sport, dport, proto)),
794 tcp_flags,
795 }
796 }
797
798 fn no_flow_meta(ts_ns: u64, caplen: u32) -> PacketMeta {
799 PacketMeta {
800 timestamp_ns: ts_ns,
801 captured_len: caplen,
802 flow_key: None,
803 tcp_flags: 0,
804 }
805 }
806
807 #[test]
810 fn test_ipnet_parse_host_v4() {
811 let net = IpNet::parse("10.0.0.1").unwrap();
812 assert!(net.contains(v4(10, 0, 0, 1)));
813 assert!(!net.contains(v4(10, 0, 0, 2)));
814 }
815
816 #[test]
817 fn test_ipnet_parse_cidr_v4() {
818 let net = IpNet::parse("10.0.0.0/8").unwrap();
819 assert!(net.contains(v4(10, 0, 0, 1)));
820 assert!(net.contains(v4(10, 255, 255, 255)));
821 assert!(!net.contains(v4(11, 0, 0, 1)));
822 }
823
824 #[test]
825 fn test_ipnet_parse_cidr_v4_slash_32() {
826 let net = IpNet::parse("192.168.1.1/32").unwrap();
827 assert!(net.contains(v4(192, 168, 1, 1)));
828 assert!(!net.contains(v4(192, 168, 1, 2)));
829 }
830
831 #[test]
832 fn test_ipnet_parse_cidr_v6() {
833 let net = IpNet::parse("2001:db8::/32").unwrap();
834 assert!(net.contains("2001:db8::1".parse().unwrap()));
835 assert!(!net.contains("2001:db9::1".parse().unwrap()));
836 }
837
838 #[test]
839 fn test_ipnet_v4_mapped_normalised() {
840 let net = IpNet::parse("10.0.0.0/8").unwrap();
843 let mapped: IpAddr = "::ffff:10.1.2.3".parse().unwrap();
844 assert!(net.contains(mapped));
845 let other_mapped: IpAddr = "::ffff:11.0.0.1".parse().unwrap();
846 assert!(!net.contains(other_mapped));
847 }
848
849 #[test]
850 fn test_ipnet_parse_invalid() {
851 assert!(IpNet::parse("not-an-ip").is_err());
852 assert!(IpNet::parse("10.0.0.0/33").is_err());
853 assert!(IpNet::parse("2001:db8::/129").is_err());
854 }
855
856 #[test]
859 fn test_port_range_single() {
860 let r = PortRange::parse("443").unwrap();
861 assert!(r.contains(443));
862 assert!(!r.contains(444));
863 }
864
865 #[test]
866 fn test_port_range_range() {
867 let r = PortRange::parse("1024-65535").unwrap();
868 assert!(r.contains(1024));
869 assert!(r.contains(65535));
870 assert!(!r.contains(1023));
871 }
872
873 #[test]
874 fn test_port_range_invalid() {
875 assert!(PortRange::parse("abc").is_err());
876 assert!(PortRange::parse("100-50").is_err()); }
878
879 #[test]
882 fn test_tcp_flags_syn_any() {
883 let f = TcpFlagsFilter::parse("SYN").unwrap();
884 assert!(f.matches(0x02)); assert!(f.matches(0x12)); assert!(!f.matches(0x10)); }
888
889 #[test]
890 fn test_tcp_flags_syn_ack_exact() {
891 let f = TcpFlagsFilter::parse("SYN+ACK:exact").unwrap();
892 assert!(f.matches(0x12)); assert!(!f.matches(0x02)); assert!(!f.matches(0x13)); }
896
897 #[test]
898 fn test_tcp_flags_invalid() {
899 assert!(TcpFlagsFilter::parse("").is_err());
900 assert!(TcpFlagsFilter::parse("INVALID").is_err());
901 }
902
903 #[test]
906 fn test_parse_proto_list() {
907 assert_eq!(parse_proto_list("tcp,udp").unwrap(), [6, 17]);
908 assert_eq!(parse_proto_list("icmp").unwrap(), [1]);
909 assert_eq!(parse_proto_list("6,17").unwrap(), [6, 17]);
910 assert!(parse_proto_list("foobar").is_err());
911 }
912
913 #[test]
916 fn test_parse_flow_ids() {
917 assert_eq!(parse_flow_ids("deadbeef").unwrap(), [0xdeadbeefu64]);
918 assert_eq!(
919 parse_flow_ids("0xdeadbeef,0xcafe1234").unwrap(),
920 [0xdeadbeefu64, 0xcafe1234u64]
921 );
922 assert!(parse_flow_ids("xyz").is_err());
923 }
924
925 #[test]
928 fn test_parse_datetime_secs_epoch() {
929 assert_eq!(parse_datetime_ns("1").unwrap(), 1_000_000_000);
931 }
932
933 #[test]
934 fn test_parse_datetime_secs_epoch_common_value() {
935 let secs: u64 = 1_000_000_000;
937 let ns = parse_datetime_ns(&secs.to_string()).unwrap();
938 assert_eq!(ns, secs * 1_000_000_000);
939 }
940
941 #[test]
942 fn test_parse_datetime_secs_matches_rfc3339() {
943 let via_int = parse_datetime_ns("1").unwrap();
945 let via_rfc = parse_datetime_ns("1970-01-01T00:00:01Z").unwrap();
946 assert_eq!(via_int, via_rfc);
947 }
948
949 #[test]
950 fn test_parse_datetime_zero() {
951 assert_eq!(parse_datetime_ns("0").unwrap(), 0);
952 }
953
954 #[test]
955 fn test_parse_datetime_rfc3339() {
956 let ns = parse_datetime_ns("1970-01-01T00:00:01Z").unwrap();
957 assert_eq!(ns, 1_000_000_000);
958 }
959
960 #[test]
961 fn test_parse_datetime_rfc3339_subsecond() {
962 let ns = parse_datetime_ns("1970-01-01T00:00:01.5Z").unwrap();
964 assert_eq!(ns, 1_500_000_000);
965 }
966
967 #[test]
968 fn test_parse_datetime_invalid() {
969 assert!(parse_datetime_ns("not-a-date").is_err());
970 }
971
972 #[test]
975 fn test_empty_filter_matches_everything() {
976 let f = Filter::default();
977 assert!(f.is_empty());
978 assert!(f.matches(&meta(0, 100, v4(1, 1, 1, 1), v4(2, 2, 2, 2), 80, 443, 6, 0)));
979 assert!(f.matches(&no_flow_meta(0, 100)));
980 }
981
982 #[test]
983 fn test_filter_time_range() {
984 let mut f = Filter::default();
985 f.from_ns = Some(1_000);
986 f.to_ns = Some(2_000);
987 assert!(f.matches(&no_flow_meta(1_000, 10)));
988 assert!(f.matches(&no_flow_meta(2_000, 10)));
989 assert!(!f.matches(&no_flow_meta(999, 10)));
990 assert!(!f.matches(&no_flow_meta(2_001, 10)));
991 }
992
993 #[test]
994 fn test_filter_packet_length() {
995 let mut f = Filter::default();
996 f.min_len = Some(50);
997 f.max_len = Some(100);
998 assert!(f.matches(&no_flow_meta(0, 50)));
999 assert!(f.matches(&no_flow_meta(0, 100)));
1000 assert!(!f.matches(&no_flow_meta(0, 49)));
1001 assert!(!f.matches(&no_flow_meta(0, 101)));
1002 }
1003
1004 #[test]
1005 fn test_filter_protocol_tcp_only() {
1006 let mut f = Filter::default();
1007 f.protocols = vec![6]; let tcp = meta(0, 100, v4(1, 1, 1, 1), v4(2, 2, 2, 2), 1234, 80, 6, 0);
1009 let udp = meta(0, 100, v4(1, 1, 1, 1), v4(2, 2, 2, 2), 1234, 53, 17, 0);
1010 assert!(f.matches(&tcp));
1011 assert!(!f.matches(&udp));
1012 }
1013
1014 #[test]
1015 fn test_filter_protocol_multiple_or() {
1016 let mut f = Filter::default();
1017 f.protocols = vec![6, 17]; assert!(f.matches(&meta(0, 100, v4(1, 1, 1, 1), v4(2, 2, 2, 2), 1, 2, 6, 0)));
1019 assert!(f.matches(&meta(0, 100, v4(1, 1, 1, 1), v4(2, 2, 2, 2), 1, 2, 17, 0)));
1020 assert!(!f.matches(&meta(0, 100, v4(1, 1, 1, 1), v4(2, 2, 2, 2), 0, 0, 1, 0))); }
1022
1023 #[test]
1024 fn test_filter_src_ip_cidr() {
1025 let mut f = Filter::default();
1026 f.src_ips = vec![IpNet::parse("10.0.0.0/8").unwrap()];
1027 assert!(f.matches(&meta(0, 60, v4(10, 1, 2, 3), v4(8, 8, 8, 8), 0, 0, 17, 0)));
1028 assert!(!f.matches(&meta(0, 60, v4(11, 0, 0, 1), v4(8, 8, 8, 8), 0, 0, 17, 0)));
1029 }
1030
1031 #[test]
1032 fn test_filter_dst_ip() {
1033 let mut f = Filter::default();
1034 f.dst_ips = vec![IpNet::parse("8.8.8.8").unwrap()];
1035 assert!(f.matches(&meta(0, 60, v4(1, 2, 3, 4), v4(8, 8, 8, 8), 0, 0, 17, 0)));
1036 assert!(!f.matches(&meta(0, 60, v4(1, 2, 3, 4), v4(1, 1, 1, 1), 0, 0, 17, 0)));
1037 }
1038
1039 #[test]
1040 fn test_filter_ip_either_endpoint() {
1041 let mut f = Filter::default();
1042 f.ips = vec![IpNet::parse("10.0.0.1").unwrap()];
1043 assert!(f.matches(&meta(0, 60, v4(10, 0, 0, 1), v4(8, 8, 8, 8), 0, 0, 17, 0)));
1045 assert!(f.matches(&meta(0, 60, v4(8, 8, 8, 8), v4(10, 0, 0, 1), 0, 0, 17, 0)));
1046 assert!(!f.matches(&meta(0, 60, v4(1, 2, 3, 4), v4(5, 6, 7, 8), 0, 0, 17, 0)));
1047 }
1048
1049 #[test]
1050 fn test_filter_src_ip_multiple_or() {
1051 let mut f = Filter::default();
1052 f.src_ips = vec![
1053 IpNet::parse("10.0.0.1").unwrap(),
1054 IpNet::parse("192.168.0.0/16").unwrap(),
1055 ];
1056 assert!(f.matches(&meta(0, 60, v4(10, 0, 0, 1), v4(8, 8, 8, 8), 0, 0, 6, 0)));
1057 assert!(f.matches(&meta(0, 60, v4(192, 168, 1, 2), v4(8, 8, 8, 8), 0, 0, 6, 0)));
1058 assert!(!f.matches(&meta(0, 60, v4(172, 16, 0, 1), v4(8, 8, 8, 8), 0, 0, 6, 0)));
1059 }
1060
1061 #[test]
1062 fn test_filter_dst_port() {
1063 let mut f = Filter::default();
1064 f.dst_ports = vec![PortRange {
1065 start: 443,
1066 end: 443,
1067 }];
1068 assert!(f.matches(&meta(
1069 0,
1070 60,
1071 v4(1, 1, 1, 1),
1072 v4(2, 2, 2, 2),
1073 1234,
1074 443,
1075 6,
1076 0
1077 )));
1078 assert!(!f.matches(&meta(0, 60, v4(1, 1, 1, 1), v4(2, 2, 2, 2), 1234, 80, 6, 0)));
1079 }
1080
1081 #[test]
1082 fn test_filter_port_range_either() {
1083 let mut f = Filter::default();
1084 f.ports = vec![PortRange {
1085 start: 8000,
1086 end: 9000,
1087 }];
1088 assert!(f.matches(&meta(
1089 0,
1090 60,
1091 v4(1, 1, 1, 1),
1092 v4(2, 2, 2, 2),
1093 8080,
1094 1234,
1095 6,
1096 0
1097 )));
1098 assert!(f.matches(&meta(
1099 0,
1100 60,
1101 v4(1, 1, 1, 1),
1102 v4(2, 2, 2, 2),
1103 1234,
1104 8080,
1105 6,
1106 0
1107 )));
1108 assert!(!f.matches(&meta(0, 60, v4(1, 1, 1, 1), v4(2, 2, 2, 2), 80, 443, 6, 0)));
1109 }
1110
1111 #[test]
1112 fn test_filter_port_ignored_for_icmp() {
1113 let mut f = Filter::default();
1115 f.dst_ports = vec![PortRange {
1116 start: 443,
1117 end: 443,
1118 }];
1119 assert!(f.matches(&meta(0, 60, v4(1, 1, 1, 1), v4(2, 2, 2, 2), 0, 0, 1, 0)));
1123 }
1124
1125 #[test]
1126 fn test_filter_flow_id() {
1127 let key = FlowKey::new(v4(10, 0, 0, 1), v4(10, 0, 0, 2), 1234, 443, 6);
1128 let id = key.flow_id(false);
1129 let mut f = Filter::default();
1130 f.flow_ids = [id].into_iter().collect();
1131
1132 let matching = PacketMeta {
1133 timestamp_ns: 0,
1134 captured_len: 60,
1135 flow_key: Some(key),
1136 tcp_flags: 0,
1137 };
1138 assert!(f.matches(&matching));
1139
1140 let other = PacketMeta {
1141 timestamp_ns: 0,
1142 captured_len: 60,
1143 flow_key: Some(FlowKey::new(v4(10, 0, 0, 3), v4(10, 0, 0, 4), 5678, 80, 6)),
1144 tcp_flags: 0,
1145 };
1146 assert!(!f.matches(&other));
1147 }
1148
1149 #[test]
1150 fn test_filter_flow_id_multiple() {
1151 let keys: Vec<FlowKey> = (0u8..4)
1152 .map(|i| {
1153 FlowKey::new(
1154 v4(10, 0, 0, i + 1),
1155 v4(10, 0, 0, 100),
1156 1000 + i as u16,
1157 80,
1158 6,
1159 )
1160 })
1161 .collect();
1162 let ids: Vec<u64> = keys.iter().map(|k| k.flow_id(false)).collect();
1163
1164 let mut f = Filter::default();
1165 f.flow_ids = ids[..3].iter().copied().collect(); for (i, key) in keys.iter().enumerate() {
1168 let pkt = PacketMeta {
1169 timestamp_ns: 0,
1170 captured_len: 60,
1171 flow_key: Some(key.clone()),
1172 tcp_flags: 0,
1173 };
1174 if i < 3 {
1175 assert!(f.matches(&pkt), "flow {i} should be allowed");
1176 } else {
1177 assert!(!f.matches(&pkt), "flow {i} should be blocked");
1178 }
1179 }
1180 }
1181
1182 #[test]
1183 fn test_filter_flow_id_empty_allows_all() {
1184 let f = Filter::default();
1186 assert!(f.flow_ids.is_empty());
1187 let pkt = meta(0, 60, v4(1, 1, 1, 1), v4(2, 2, 2, 2), 1234, 80, 6, 0);
1188 assert!(f.matches(&pkt));
1189 }
1190
1191 #[test]
1192 fn test_filter_flow_id_non_ip_blocked() {
1193 let key = FlowKey::new(v4(10, 0, 0, 1), v4(10, 0, 0, 2), 1234, 443, 6);
1196 let mut f = Filter::default();
1197 f.flow_ids = [key.flow_id(false)].into_iter().collect();
1198
1199 let no_key = PacketMeta {
1200 timestamp_ns: 0,
1201 captured_len: 60,
1202 flow_key: None,
1203 tcp_flags: 0,
1204 };
1205 assert!(!f.matches(&no_key));
1206 }
1207
1208 #[test]
1209 fn test_filter_tcp_flags() {
1210 let mut f = Filter::default();
1211 f.tcp_flags = Some(TcpFlagsFilter::parse("SYN").unwrap());
1212
1213 assert!(f.matches(&meta(
1215 0,
1216 60,
1217 v4(1, 1, 1, 1),
1218 v4(2, 2, 2, 2),
1219 1234,
1220 80,
1221 6,
1222 0x02
1223 )));
1224 assert!(!f.matches(&meta(
1226 0,
1227 60,
1228 v4(1, 1, 1, 1),
1229 v4(2, 2, 2, 2),
1230 1234,
1231 80,
1232 6,
1233 0x10
1234 )));
1235 }
1236
1237 #[test]
1238 fn test_filter_and_composition() {
1239 let mut f = Filter::default();
1241 f.protocols = vec![6];
1242 f.dst_ports = vec![PortRange {
1243 start: 443,
1244 end: 443,
1245 }];
1246 f.src_ips = vec![IpNet::parse("10.0.0.0/8").unwrap()];
1247
1248 assert!(f.matches(&meta(
1250 0,
1251 60,
1252 v4(10, 1, 2, 3),
1253 v4(8, 8, 8, 8),
1254 5000,
1255 443,
1256 6,
1257 0
1258 )));
1259 assert!(!f.matches(&meta(
1261 0,
1262 60,
1263 v4(10, 1, 2, 3),
1264 v4(8, 8, 8, 8),
1265 5000,
1266 443,
1267 17,
1268 0
1269 )));
1270 assert!(!f.matches(&meta(
1272 0,
1273 60,
1274 v4(10, 1, 2, 3),
1275 v4(8, 8, 8, 8),
1276 5000,
1277 80,
1278 6,
1279 0
1280 )));
1281 assert!(!f.matches(&meta(
1283 0,
1284 60,
1285 v4(11, 0, 0, 1),
1286 v4(8, 8, 8, 8),
1287 5000,
1288 443,
1289 6,
1290 0
1291 )));
1292 }
1293
1294 #[test]
1295 fn test_filter_non_ip_fails_when_flow_filter_active() {
1296 let mut f = Filter::default();
1297 f.protocols = vec![6];
1298 assert!(!f.matches(&no_flow_meta(0, 100)));
1299 }
1300
1301 #[test]
1302 fn test_filter_non_ip_passes_when_only_length_filter() {
1303 let mut f = Filter::default();
1304 f.min_len = Some(50);
1305 assert!(f.matches(&no_flow_meta(0, 60)));
1306 assert!(!f.matches(&no_flow_meta(0, 40)));
1307 }
1308}