filter: add unit tests + chain_summary method
5 new unit tests in filter::table::tests: - drop_rule_actually_drops (R33 bug regression test) - accept_rule_actually_accepts (R33 bug regression test) - rule_matching_other_hook_does_not_apply (hook isolation) - default_policy_applied_when_no_rules (fallback) - reset_counters_clears_metrics_keeps_rules (R34 method coverage) All 5 tests pass — proves the critical verdict bug from R33 is fixed and reset_counters from R34 works correctly. Also adds FilterTable::chain_summary() returning: 'input: pkts=12 bytes=5678 policy=ACCEPT' (per-chain line) for use by future netcfg summary extension.
This commit is contained in:
@@ -177,6 +177,25 @@ impl FilterTable {
|
||||
out
|
||||
}
|
||||
|
||||
/// Compact per-chain summary for netcfg summary node.
|
||||
/// Returns lines like:
|
||||
/// input: pkts=12 bytes=5678 policy=ACCEPT
|
||||
pub fn chain_summary(&self) -> String {
|
||||
let mut out = String::new();
|
||||
for &hook in &Hook::ALL {
|
||||
let (pkts, bytes) = self.chain_counters.get(&hook).copied().unwrap_or((0, 0));
|
||||
let policy = self.default_policy.get(&hook).copied().unwrap_or(Verdict::Accept);
|
||||
out.push_str(&alloc::format!(
|
||||
"{}: pkts={} bytes={} policy={}\n",
|
||||
hook.name(),
|
||||
pkts,
|
||||
bytes,
|
||||
policy.name()
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn set_default_policy(&mut self, hook: Hook, verdict: Verdict) {
|
||||
self.default_policy.insert(hook, verdict);
|
||||
}
|
||||
@@ -395,4 +414,105 @@ fn parse_port(value: &str) -> core::result::Result<u16, ParseError> {
|
||||
port_str
|
||||
.parse::<u16>()
|
||||
.map_err(|_| ParseError::BadPort(value.to_string()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::rc::Rc;
|
||||
use smoltcp::wire::IpAddress;
|
||||
use smoltcp::time::Instant;
|
||||
|
||||
fn make_ctx(hook: Hook, src: IpAddress, dst: IpAddress) -> PacketContext<'static> {
|
||||
PacketContext {
|
||||
hook,
|
||||
in_dev: None,
|
||||
out_dev: None,
|
||||
src_addr: src,
|
||||
dst_addr: dst,
|
||||
protocol: 6,
|
||||
src_port: Some(1234),
|
||||
dst_port: Some(80),
|
||||
packet: &[],
|
||||
}
|
||||
}
|
||||
|
||||
fn make_drop_rule(hook: Hook) -> FilterRule {
|
||||
FilterRule {
|
||||
id: 1,
|
||||
hook,
|
||||
src_addr: None,
|
||||
src_prefix_len: 0,
|
||||
dst_addr: None,
|
||||
dst_prefix_len: 0,
|
||||
protocol: None,
|
||||
src_port: None,
|
||||
dst_port: None,
|
||||
in_dev: None,
|
||||
out_dev: None,
|
||||
state_match: None,
|
||||
verdict: Verdict::Drop,
|
||||
match_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_accept_rule(hook: Hook) -> FilterRule {
|
||||
let mut r = make_drop_rule(hook);
|
||||
r.id = 2;
|
||||
r.verdict = Verdict::Accept;
|
||||
r
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_rule_actually_drops() {
|
||||
let mut t = FilterTable::new();
|
||||
t.rules.push(make_drop_rule(Hook::InputLocal));
|
||||
let ctx = make_ctx(Hook::InputLocal, IpAddress::v4(10, 0, 0, 1), IpAddress::v4(192, 168, 1, 1));
|
||||
assert_eq!(t.evaluate(&ctx, Instant::from_millis(0)), Verdict::Drop,
|
||||
"DROP rule must drop the packet (regression test for R33 bug fix)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accept_rule_actually_accepts() {
|
||||
let mut t = FilterTable::new();
|
||||
t.rules.push(make_accept_rule(Hook::InputLocal));
|
||||
let ctx = make_ctx(Hook::InputLocal, IpAddress::v4(10, 0, 0, 1), IpAddress::v4(192, 168, 1, 1));
|
||||
assert_eq!(t.evaluate(&ctx, Instant::from_millis(0)), Verdict::Accept,
|
||||
"ACCEPT rule must accept the packet (regression test)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_matching_other_hook_does_not_apply() {
|
||||
let mut t = FilterTable::new();
|
||||
t.rules.push(make_drop_rule(Hook::OutputLocal));
|
||||
let ctx = make_ctx(Hook::InputLocal, IpAddress::v4(10, 0, 0, 1), IpAddress::v4(192, 168, 1, 1));
|
||||
assert_eq!(t.evaluate(&ctx, Instant::from_millis(0)), Verdict::Accept,
|
||||
"Rule for OUTPUT must not affect INPUT chain — should fall through to default policy");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_policy_applied_when_no_rules() {
|
||||
let mut t = FilterTable::new();
|
||||
t.set_default_policy(Hook::InputLocal, Verdict::Drop);
|
||||
let ctx = make_ctx(Hook::InputLocal, IpAddress::v4(10, 0, 0, 1), IpAddress::v4(192, 168, 1, 1));
|
||||
assert_eq!(t.evaluate(&ctx, Instant::from_millis(0)), Verdict::Drop,
|
||||
"Default policy applies when no matching rule");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_counters_clears_metrics_keeps_rules() {
|
||||
let mut t = FilterTable::new();
|
||||
t.rules.push(make_drop_rule(Hook::InputLocal));
|
||||
let ctx = make_ctx(Hook::InputLocal, IpAddress::v4(10, 0, 0, 1), IpAddress::v4(192, 168, 1, 1));
|
||||
let _ = t.evaluate(&ctx, Instant::from_millis(0));
|
||||
assert_eq!(t.rules[0].match_count, 1);
|
||||
let (_p, _b) = t.chain_counters.get(&Hook::InputLocal).copied().unwrap_or((0, 0));
|
||||
t.reset_counters();
|
||||
assert_eq!(t.rules[0].match_count, 0,
|
||||
"reset_counters must clear rule.match_count");
|
||||
assert_eq!(t.chain_counters.get(&Hook::InputLocal).copied().unwrap_or((0, 0)), (0, 0),
|
||||
"reset_counters must clear chain_counters");
|
||||
assert_eq!(t.rules.len(), 1,
|
||||
"reset_counters must preserve rules");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user