Skip to content

Commit 0e29c5a

Browse files
qmonnetclaude
andcommitted
test(flow-filter): Add unit tests for flow-filter pipeline stage
Add unit tests to test the various modules in the recent flow-filter crate. Most of these were generated by Claude, with manual control and adjustments. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Quentin Monnet <qmo@qmon.net>
1 parent a82073a commit 0e29c5a

File tree

5 files changed

+1229
-0
lines changed

5 files changed

+1229
-0
lines changed

flow-filter/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ tracectl = { workspace = true }
1616
tracing = { workspace = true }
1717

1818
[dev-dependencies]
19+
lpm = { workspace = true, features = ["testing"] }
1920
tracing-test = { workspace = true, features = [] }

flow-filter/src/ip_port_prefix_trie.rs

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,228 @@ where
115115
Self::new()
116116
}
117117
}
118+
119+
#[cfg(test)]
120+
mod tests {
121+
use super::*;
122+
use lpm::prefix::{PortRange, Prefix};
123+
use std::collections::BTreeSet;
124+
125+
#[derive(Debug, Clone)]
126+
enum TestValue {
127+
AnyPort,
128+
Ranges(BTreeSet<PortRange>),
129+
}
130+
131+
impl ValueWithAssociatedRanges for TestValue {
132+
fn covers_all_ports(&self) -> bool {
133+
match self {
134+
TestValue::AnyPort => true,
135+
TestValue::Ranges(ranges) => {
136+
ranges.iter().fold(0, |sum, range| sum + range.len()) == PortRange::MAX_LENGTH
137+
}
138+
}
139+
}
140+
141+
fn covers_port(&self, port: u16) -> bool {
142+
match self {
143+
TestValue::AnyPort => true,
144+
TestValue::Ranges(ranges) => ranges.iter().any(|range| range.contains(&port)),
145+
}
146+
}
147+
}
148+
149+
#[test]
150+
fn test_new() {
151+
let trie: IpPortPrefixTrie<TestValue> = IpPortPrefixTrie::new();
152+
assert!(trie.lookup(&"192.168.1.1".parse().unwrap(), None).is_none());
153+
}
154+
155+
#[test]
156+
fn test_from() {
157+
let prefix = Prefix::from("192.168.1.0/24");
158+
let value = TestValue::AnyPort;
159+
let trie = IpPortPrefixTrie::from(prefix, value);
160+
161+
let result = trie.lookup(&"192.168.1.5".parse().unwrap(), None);
162+
assert!(result.is_some());
163+
let (matched_prefix, _) = result.unwrap();
164+
assert_eq!(matched_prefix, prefix);
165+
}
166+
167+
#[test]
168+
fn test_insert_and_lookup_any_port() {
169+
let mut trie = IpPortPrefixTrie::new();
170+
let prefix = Prefix::from("10.0.0.0/16");
171+
let value = TestValue::AnyPort;
172+
173+
trie.insert(prefix, value);
174+
175+
// Should match with any port
176+
let result = trie.lookup(&"10.0.1.5".parse().unwrap(), Some(80));
177+
assert!(result.is_some());
178+
let (matched_prefix, matched_value) = result.unwrap();
179+
assert_eq!(matched_prefix, prefix);
180+
assert!(matches!(matched_value, TestValue::AnyPort));
181+
182+
// Should match without port
183+
let result = trie.lookup(&"10.0.1.5".parse().unwrap(), None);
184+
assert!(result.is_some());
185+
}
186+
187+
#[test]
188+
fn test_insert_and_lookup_with_port_ranges() {
189+
let mut trie = IpPortPrefixTrie::new();
190+
let prefix = Prefix::from("172.16.0.0/12");
191+
let ranges = BTreeSet::from([PortRange::new(80, 90).unwrap()]);
192+
let value = TestValue::Ranges(ranges);
193+
194+
trie.insert(prefix, value);
195+
196+
// Should match port in range
197+
let result = trie.lookup(&"172.16.5.10".parse().unwrap(), Some(85));
198+
assert!(result.is_some());
199+
let (matched_prefix, _) = result.unwrap();
200+
assert_eq!(matched_prefix, prefix);
201+
202+
// Should not match port outside range
203+
let result = trie.lookup(&"172.16.5.10".parse().unwrap(), Some(100));
204+
assert!(result.is_none());
205+
206+
// Should not match without port
207+
let result = trie.lookup(&"172.16.5.10".parse().unwrap(), None);
208+
assert!(result.is_none());
209+
}
210+
211+
#[test]
212+
fn test_lookup_longest_prefix_match_no_ports() {
213+
let mut trie = IpPortPrefixTrie::new();
214+
215+
// Insert prefix with port range
216+
let prefix_with_ports = Prefix::from("192.168.0.0/24");
217+
let ranges = BTreeSet::from([PortRange::new(80, 90).unwrap()]);
218+
trie.insert(prefix_with_ports, TestValue::Ranges(ranges));
219+
220+
// Insert prefix covering all ports
221+
let prefix_alone = Prefix::from("192.168.1.0/24");
222+
trie.insert(prefix_alone, TestValue::AnyPort);
223+
224+
// Match wihout port
225+
let result = trie.lookup(&"192.168.1.5".parse().unwrap(), None);
226+
assert!(result.is_some());
227+
let (matched_prefix, _) = result.unwrap();
228+
assert_eq!(matched_prefix, prefix_alone);
229+
230+
// Match with a port
231+
let result = trie.lookup(&"192.168.1.5".parse().unwrap(), Some(443));
232+
assert!(result.is_some());
233+
let (matched_prefix, _) = result.unwrap();
234+
assert_eq!(matched_prefix, prefix_alone);
235+
236+
// Fail to match prefix_with_ports without a port
237+
let result = trie.lookup(&"192.168.0.5".parse().unwrap(), None);
238+
assert!(result.is_none());
239+
240+
// Match with a port
241+
let result = trie.lookup(&"192.168.0.5".parse().unwrap(), Some(80));
242+
assert!(result.is_some());
243+
let (matched_prefix, _) = result.unwrap();
244+
assert_eq!(matched_prefix, prefix_with_ports);
245+
}
246+
247+
#[test]
248+
fn test_lookup_longest_prefix_match_with_ports() {
249+
let mut trie = IpPortPrefixTrie::new();
250+
251+
// Insert broader prefix
252+
let prefix_16 = Prefix::from("192.168.0.0/16");
253+
let ranges = BTreeSet::from([PortRange::new(80, 90).unwrap()]);
254+
trie.insert(prefix_16, TestValue::Ranges(ranges));
255+
256+
// Insert more specific prefix
257+
let prefix_24 = Prefix::from("192.168.1.0/24");
258+
let ranges = BTreeSet::from([PortRange::new(443, 443).unwrap()]);
259+
trie.insert(prefix_24, TestValue::Ranges(ranges));
260+
261+
// Without port, there is not match
262+
let result = trie.lookup(&"192.168.1.5".parse().unwrap(), None);
263+
assert!(result.is_none());
264+
265+
// Based on port, we match the more specific prefix
266+
let result = trie.lookup(&"192.168.1.5".parse().unwrap(), Some(443));
267+
assert!(result.is_some());
268+
let (matched_prefix, _) = result.unwrap();
269+
assert_eq!(matched_prefix, prefix_24);
270+
271+
// Based on port, we match the broader prefix
272+
let result = trie.lookup(&"192.168.1.5".parse().unwrap(), Some(80));
273+
assert!(result.is_some());
274+
let (matched_prefix, _) = result.unwrap();
275+
assert_eq!(matched_prefix, prefix_16);
276+
}
277+
278+
#[test]
279+
fn test_get_mut() {
280+
let mut trie = IpPortPrefixTrie::new();
281+
let prefix = Prefix::from("203.0.113.0/24");
282+
let ranges = BTreeSet::from([PortRange::new(8080, 8090).unwrap()]);
283+
trie.insert(prefix, TestValue::Ranges(ranges));
284+
285+
// Modify the value
286+
if let Some(value) = trie.get_mut(prefix) {
287+
*value = TestValue::AnyPort;
288+
}
289+
290+
// Should now match with any port
291+
let result = trie.lookup(&"203.0.113.5".parse().unwrap(), Some(9999));
292+
assert!(result.is_some());
293+
let (_, matched_value) = result.unwrap();
294+
assert!(matches!(matched_value, TestValue::AnyPort));
295+
}
296+
297+
#[test]
298+
fn test_ipv6_lookup() {
299+
let mut trie = IpPortPrefixTrie::new();
300+
let prefix = Prefix::from("2001:db8::/32");
301+
trie.insert(prefix, TestValue::AnyPort);
302+
303+
let result = trie.lookup(&"2001:db8::1".parse().unwrap(), None);
304+
assert!(result.is_some());
305+
let (matched_prefix, _) = result.unwrap();
306+
assert_eq!(matched_prefix, prefix);
307+
308+
let result = trie.lookup(&"2001:db9::1".parse().unwrap(), None);
309+
assert!(result.is_none());
310+
}
311+
312+
#[test]
313+
fn test_covers_all_ports() {
314+
let any_port = TestValue::AnyPort;
315+
assert!(any_port.covers_all_ports());
316+
317+
let mut ranges = BTreeSet::new();
318+
ranges.insert(PortRange::new(0, 32767).unwrap());
319+
ranges.insert(PortRange::new(32768, 65535).unwrap());
320+
let full_range = TestValue::Ranges(ranges);
321+
assert!(full_range.covers_all_ports());
322+
323+
let partial_ranges = BTreeSet::from([PortRange::new(80, 443).unwrap()]);
324+
let partial_range = TestValue::Ranges(partial_ranges);
325+
assert!(!partial_range.covers_all_ports());
326+
}
327+
328+
#[test]
329+
fn test_covers_port() {
330+
let any_port = TestValue::AnyPort;
331+
assert!(any_port.covers_port(80));
332+
assert!(any_port.covers_port(65535));
333+
334+
let mut ranges = BTreeSet::new();
335+
ranges.insert(PortRange::new(80, 80).unwrap());
336+
ranges.insert(PortRange::new(443, 443).unwrap());
337+
let specific_ports = TestValue::Ranges(ranges);
338+
assert!(specific_ports.covers_port(80));
339+
assert!(specific_ports.covers_port(443));
340+
assert!(!specific_ports.covers_port(8080));
341+
}
342+
}

0 commit comments

Comments
 (0)