diff --git a/resources/test/json/flow.json b/resources/test/json/flow.json new file mode 100644 index 0000000..bc4b72c --- /dev/null +++ b/resources/test/json/flow.json @@ -0,0 +1,68 @@ +{ + "nftables": [ + { + "metainfo": { + "version": "1.0.9", + "release_name": "Old Doc Yak #3", + "json_schema_version": 1 + } + }, + { + "table": { + "family": "inet", + "name": "named_counter_demo", + "handle": 3 + } + }, + { + "flowtable": { + "family": "inet", + "name": "flowed", + "table": "named_counter_demo", + "handle": 2, + "hook": "ingress", + "prio": 0, + "dev": "lo" + } + }, + { + "chain": { + "family": "inet", + "table": "named_counter_demo", + "name": "forward", + "handle": 1, + "type": "filter", + "hook": "forward", + "prio": 0, + "policy": "accept" + } + }, + { + "rule": { + "family": "inet", + "table": "named_counter_demo", + "chain": "forward", + "handle": 3, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": "established" + } + }, + { + "flow": { + "op": "add", + "flowtable": "@flowed" + } + } + ] + } + } + ] +} diff --git a/resources/test/nft/flow.nft b/resources/test/nft/flow.nft new file mode 100644 index 0000000..4e86b1c --- /dev/null +++ b/resources/test/nft/flow.nft @@ -0,0 +1,15 @@ +#!/sbin/nft -f + +flush ruleset + +table inet named_counter_demo { + flowtable flowed { + hook ingress priority filter + devices = { lo } + } + + chain forward { + type filter hook forward priority filter; policy accept; + ct state established flow add @flowed + } +} diff --git a/tests/json_tests.rs b/tests/json_tests.rs index e1d1c37..fa2c846 100644 --- a/tests/json_tests.rs +++ b/tests/json_tests.rs @@ -1,5 +1,5 @@ -use nftables::expr::{Expression, Meta, MetaKey, NamedExpression}; -use nftables::stmt::{Counter, Match, Operator, Queue, Statement}; +use nftables::expr::{self, Expression, Meta, MetaKey, NamedExpression}; +use nftables::stmt::{self, Counter, Match, Operator, Queue, Statement}; use nftables::{schema::*, types::*}; use serde_json::json; use std::fs::{self, File}; @@ -24,8 +24,12 @@ fn test_deserialize_json_files() { #[test] fn test_chain_table_rule_inet() { - // nft add table inet some_inet_table - // nft add chain inet some_inet_table some_inet_chain '{ type filter hook forward priority 0; policy accept; }' + // Equivalent nft command: + // ``` + // nft "add table inet some_inet_table; + // add chain inet some_inet_table some_inet_chain + // '{ type filter hook forward priority 0; policy accept; }'" + // ``` let expected: Nftables = Nftables { objects: vec![ NfObject::CmdObject(NfCmd::Add(NfListObject::Table(Table { @@ -47,7 +51,88 @@ fn test_chain_table_rule_inet() { }))), ], }; - let json = json!({"nftables":[{"add":{"table":{"family":"inet","name":"some_inet_table"}}},{"add":{"chain":{"family":"inet","table":"some_inet_table","name":"some_inet_chain","type":"filter","hook":"forward","policy":"accept"}}}]}); + let json = json!({"nftables":[ + {"add":{"table":{"family":"inet","name":"some_inet_table"}}}, + {"add":{"chain":{"family":"inet","table":"some_inet_table", + "name":"some_inet_chain","type":"filter","hook":"forward","policy":"accept"}}} + ]}); + println!("{}", &json); + let parsed: Nftables = serde_json::from_value(json).unwrap(); + assert_eq!(expected, parsed); +} + +#[test] +/// Test JSON serialization of flow and flowtable. +fn test_flowtable() { + // equivalent nft command: + // ``` + // nft 'flush ruleset; add table inet some_inet_table; + // add chain inet some_inet_table forward; + // add flowtable inet some_inet_table flowed { hook ingress priority filter; devices = { lo }; }; + // add rule inet some_inet_table forward ct state established flow add @flowed' + // ``` + let expected: Nftables = Nftables { + objects: vec![ + NfObject::ListObject(Box::new(NfListObject::Table(Table { + family: NfFamily::INet, + name: "some_inet_table".to_string(), + handle: None, + }))), + NfObject::ListObject(Box::new(NfListObject::FlowTable(FlowTable { + family: NfFamily::INet, + table: "some_inet_table".to_string(), + name: "flowed".to_string(), + handle: None, + hook: Some(NfHook::Ingress), + prio: Some(0), + dev: Some(vec!["lo".to_string()]), + }))), + NfObject::ListObject(Box::new(NfListObject::Chain(Chain { + family: NfFamily::INet, + table: "some_inet_table".to_string(), + name: "some_inet_chain".to_string(), + newname: None, + handle: None, + _type: Some(NfChainType::Filter), + hook: Some(NfHook::Forward), + prio: None, + dev: None, + policy: Some(NfChainPolicy::Accept), + }))), + NfObject::ListObject(Box::new(NfListObject::Rule(Rule { + family: NfFamily::INet, + table: "some_inet_table".to_string(), + chain: "some_inet_chain".to_string(), + expr: vec![ + Statement::Flow(stmt::Flow { + op: stmt::SetOp::Add, + flowtable: "@flowed".to_string(), + }), + Statement::Match(Match { + left: Expression::Named(NamedExpression::CT(expr::CT { + key: "state".to_string(), + family: None, + dir: None, + })), + op: Operator::IN, + right: Expression::String("established".to_string()), + }), + ], + handle: None, + index: None, + comment: None, + }))), + ], + }; + let json = json!({"nftables":[ + {"table":{"family":"inet","name":"some_inet_table"}}, + {"flowtable":{"family":"inet","table":"some_inet_table","name":"flowed", + "hook":"ingress","prio":0,"dev":["lo"]}}, + {"chain":{"family":"inet","table":"some_inet_table","name":"some_inet_chain", + "type":"filter","hook":"forward","policy":"accept"}}, + {"rule":{"family":"inet","table":"some_inet_table","chain":"some_inet_chain", + "expr":[{"flow":{"op":"add","flowtable":"@flowed"}}, + {"match":{"left":{"ct":{"key":"state"}},"right":"established","op":"in"}}]}}]}); println!("{}", &json); let parsed: Nftables = serde_json::from_value(json).unwrap(); assert_eq!(expected, parsed); @@ -55,7 +140,11 @@ fn test_chain_table_rule_inet() { #[test] fn test_insert() { - // nft insert rule inet some_inet_table some_inet_chain position 0 iifname "br-lan" oifname "wg_exit" counter accept + // Equivalent nft command: + // ``` + // nft 'insert rule inet some_inet_table some_inet_chain position 0 + // iifname "br-lan" oifname "wg_exit" counter accept' + // ``` let expected: Nftables = Nftables { objects: vec![NfObject::CmdObject(NfCmd::Insert(NfListObject::Rule( Rule { @@ -86,7 +175,12 @@ fn test_insert() { }, )))], }; - let json = json!({"nftables":[{"insert":{"rule":{"family":"inet","table":"some_inet_table","chain":"some_inet_chain","expr":[{"match":{"left":{"meta":{"key":"iifname"}},"right":"br-lan","op":"=="}},{"match":{"left":{"meta":{"key":"oifname"}},"right":"wg_exit","op":"=="}},{"counter":null},{"accept":null}],"index":0,"comment":null}}}]}); + let json = json!({"nftables":[{"insert": + {"rule":{"family":"inet","table":"some_inet_table","chain":"some_inet_chain","expr":[ + {"match":{"left":{"meta":{"key":"iifname"}},"right":"br-lan","op":"=="}}, + {"match":{"left":{"meta":{"key":"oifname"}},"right":"wg_exit","op":"=="}}, + {"counter":null},{"accept":null} + ],"index":0,"comment":null}}}]}); println!("{}", &json); let parsed: Nftables = serde_json::from_value(json).unwrap(); assert_eq!(expected, parsed);