filter: add --ctstate flag + case-insensitive states + CSV support

Rule parser now accepts iptables-style:
  ACCEPT input -p tcp --ctstate ESTABLISHED,RELATED
  DROP forward state NEW

Both 'state' and '--ctstate' keywords work. States are case-
insensitive: NEW/New/new, ESTABLISHED/Established/established,
RELATED/Related/related, INVALID/Invalid/invalid.

Comma-separated lists (e.g. 'ESTABLISHED,RELATED') use the FIRST
recognized state only — FilterRule stores a single StateMatch field.

Docstring updated with new examples.

All 31 tests pass.
This commit is contained in:
Red Bear OS
2026-07-09 10:01:18 +03:00
parent b50fb644a9
commit d1f76a3aa1
+12 -7
View File
@@ -11,13 +11,14 @@
//!
//! ```text
//! ACTION CHAIN [-i IFACE] [-o IFACE] [-p PROTO] [-s ADDR[/LEN]] [-d ADDR[/LEN]]
//! [--sport PORT] [--dport PORT]
//! [--sport PORT] [--dport PORT] [state STATE|--ctstate STATE[,STATE...]]
//! ```
//!
//! Examples:
//! - `ACCEPT input -p tcp --dport 80`
//! - `DROP input -s 10.0.0.0/8`
//! - `ACCEPT output -d 192.168.1.0/24 --sport 1024`
//! - `ACCEPT input -p tcp --ctstate ESTABLISHED,RELATED`
extern crate alloc;
@@ -355,13 +356,17 @@ pub fn parse_rule(line: &str) -> core::result::Result<FilterRule, ParseError> {
let value = tokens.next().ok_or(ParseError::Msg("missing --dport value"))?;
rule.dst_port = Some(parse_port(value)?);
}
"state" => {
"state" | "--ctstate" => {
let value = tokens.next().ok_or(ParseError::Msg("missing state value"))?;
rule.state_match = Some(match value {
"new" => StateMatch::New,
"established" => StateMatch::Established,
"related" => StateMatch::Related,
"invalid" => StateMatch::Invalid,
// Accept comma-separated list (e.g. "ESTABLISHED,RELATED"),
// using the FIRST recognized state. The FilterRule struct
// supports a single state_match field.
let first = value.split(',').next().unwrap_or(value);
rule.state_match = Some(match first {
"new" | "NEW" => StateMatch::New,
"established" | "ESTABLISHED" => StateMatch::Established,
"related" | "RELATED" => StateMatch::Related,
"invalid" | "INVALID" => StateMatch::Invalid,
_ => return Err(ParseError::Msg("unknown state value")),
});
}