1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
//! Handles stream processing of commands and events.
use chrono::NaiveDateTime;
use std::collections::BTreeMap;
use std::io::Write;
use crate::parser::Packet;
/// Signals are pre-defined indicators that are seen in a packet stream.
pub struct Signal {
/// Where in the packet stream we see this signal.
pub index: usize,
/// Timestamp where this signal is seen.
pub ts: NaiveDateTime,
/// Tag identifying the signal. Signals must be pre-defined so we're going
/// to enforce a static lifetime here.
pub tag: &'static str,
}
/// Trait that describes a single rule processor. A rule should be used to represent a certain type
/// of analysis (for example: ACL Connections rule may keep track of all ACL connections and report
/// on failed connections).
pub trait Rule {
/// Process a single packet.
fn process(&mut self, packet: &Packet);
/// Generate a report for this rule based on the input stream so far. Usually, this should
/// report on the instances of this rule that were discovered or any error conditions that are
/// relevant to this rule.
fn report(&self, writer: &mut dyn Write);
/// Report on any signals seen by this rule on the input stream so far. Signals are
/// structured indicators that specify a specific type of condition that are pre-defined and
/// used to bucket interesting behavior. Not all reportable events are signals but all signals
/// are reportable events.
fn report_signals(&self) -> &[Signal];
}
/// Grouping of rules. This is used to make it easier to enable/disable certain rules for
/// processing a file.
pub struct RuleGroup {
rules: Vec<Box<dyn Rule>>,
}
impl RuleGroup {
pub fn new() -> Self {
RuleGroup { rules: vec![] }
}
pub fn add_rule(&mut self, rule: Box<dyn Rule>) {
self.rules.push(rule);
}
pub fn process(&mut self, packet: &Packet) {
for rule in &mut self.rules {
rule.process(packet);
}
}
pub fn report(&self, writer: &mut dyn Write) {
for rule in &self.rules {
rule.report(writer);
}
}
pub fn report_signals(&self, writer: &mut dyn Write) {
for rule in &self.rules {
for signal in rule.report_signals() {
let _ = writeln!(writer, "({}, {}, {})", signal.index, signal.ts, signal.tag);
}
}
}
}
/// Main entry point to process input data and run rules on them.
pub struct RuleEngine {
groups: BTreeMap<String, RuleGroup>,
}
impl RuleEngine {
pub fn new() -> Self {
RuleEngine { groups: BTreeMap::new() }
}
pub fn add_rule_group(&mut self, name: String, group: RuleGroup) {
self.groups.insert(name, group);
}
/// Consume a packet and run it through the various rules processors.
pub fn process(&mut self, packet: Packet) {
for group in self.groups.values_mut() {
group.process(&packet);
}
}
pub fn report(&self, writer: &mut dyn Write) {
for group in self.groups.values() {
group.report(writer);
}
}
pub fn report_signals(&self, writer: &mut dyn Write) {
for group in self.groups.values() {
group.report_signals(writer);
}
}
}
|