diff --git a/resources/test/fixtures/README.md b/resources/test/fixtures/README.md new file mode 100644 index 0000000..19a1c03 --- /dev/null +++ b/resources/test/fixtures/README.md @@ -0,0 +1 @@ +These files are where either generated by a specific nftables version or by hand to test specific json parsing behavior. diff --git a/resources/test/fixtures/single-fib-flag-1.json b/resources/test/fixtures/single-fib-flag-1.json new file mode 100644 index 0000000..56b21d3 --- /dev/null +++ b/resources/test/fixtures/single-fib-flag-1.json @@ -0,0 +1,53 @@ +{ + "nftables": [ + { + "metainfo": { + "version": "1.1.0", + "release_name": "some name", + "json_schema_version": 1 + } + }, + { + "table": { + "family": "ip", + "name": "test", + "handle": 1 + } + }, + { + "chain": { + "family": "ip", + "table": "test", + "name": "prerouting", + "handle": 1 + } + }, + { + "rule": { + "family": "ip", + "table": "test", + "chain": "prerouting", + "handle": 2, + "expr": [ + { + "match": { + "op": "==", + "left": { + "fib": { + "result": "type", + "flags": [ + "daddr" + ] + } + }, + "right": "local" + } + }, + { + "accept": null + } + ] + } + } + ] +} diff --git a/resources/test/fixtures/single-fib-flag-2.json b/resources/test/fixtures/single-fib-flag-2.json new file mode 100644 index 0000000..f3a845a --- /dev/null +++ b/resources/test/fixtures/single-fib-flag-2.json @@ -0,0 +1,51 @@ +{ + "nftables": [ + { + "metainfo": { + "version": "1.1.0", + "release_name": "some name", + "json_schema_version": 1 + } + }, + { + "table": { + "family": "ip", + "name": "test", + "handle": 1 + } + }, + { + "chain": { + "family": "ip", + "table": "test", + "name": "prerouting", + "handle": 1 + } + }, + { + "rule": { + "family": "ip", + "table": "test", + "chain": "prerouting", + "handle": 2, + "expr": [ + { + "match": { + "op": "==", + "left": { + "fib": { + "result": "type", + "flags": "daddr" + } + }, + "right": "local" + } + }, + { + "accept": null + } + ] + } + } + ] +} diff --git a/src/expr.rs b/src/expr.rs index c4f6142..2a8251c 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize}; use std::{borrow::Cow, collections::HashSet}; use crate::stmt::{Counter, JumpTarget, Statement}; +use crate::visitor::deserialize_flags; +use strum_macros::EnumString; #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(untagged)] @@ -300,7 +302,7 @@ pub struct SctpChunk<'a> { #[serde(rename = "meta")] /// Create a reference to packet meta data. /// -/// See [this page](https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_metainformation) +/// See [this page](https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_metainformation) /// for more information. pub struct Meta { /// The packet [meta data key](MetaKey). @@ -589,6 +591,7 @@ impl Default for SymHash { pub struct Fib { /// The data to be queried by fib lookup. pub result: FibResult, + #[serde(deserialize_with = "deserialize_flags")] /// The tuple of elements ([FibFlags](FibFlag)) that is used as input to the /// fib lookup functions. pub flags: HashSet, @@ -618,8 +621,11 @@ pub enum FibResult { Type, } -#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash, JsonSchema)] +#[derive( + Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, EnumString, Hash, JsonSchema, +)] #[serde(rename_all = "lowercase")] +#[strum(serialize_all = "lowercase")] /// Represents flags for `fib` lookup. pub enum FibFlag { /// Consider the source address of a packet. diff --git a/src/visitor.rs b/src/visitor.rs index a34d2f2..3092463 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -113,3 +113,49 @@ where None => s.serialize_some(&0_usize), } } + +/// Deserialize string or array of strings into the given HashSet type. +pub fn deserialize_flags<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, + T: FromStr + Eq + core::hash::Hash + Deserialize<'de>, + ::Err: std::fmt::Display, +{ + struct FlagSet(PhantomData); + impl<'de, T> de::Visitor<'de> for FlagSet + where + T: FromStr + Eq + core::hash::Hash + Deserialize<'de>, + ::Err: std::fmt::Display, + { + type Value = HashSet; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("single string or list of strings") + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(HashSet::default()) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + ::Err: std::fmt::Display, + { + let mut h: HashSet = HashSet::new(); + h.insert(T::from_str(value).map_err(::custom)?); + Ok(h) + } + + fn visit_seq(self, visitor: S) -> Result + where + S: de::SeqAccess<'de>, + { + Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor)) + } + } + deserializer.deserialize_any(FlagSet(PhantomData)) +} diff --git a/tests/fixtures.rs b/tests/fixtures.rs new file mode 100644 index 0000000..6986374 --- /dev/null +++ b/tests/fixtures.rs @@ -0,0 +1,26 @@ +use std::{fs::File, io::BufReader}; + +use nftables::schema::Nftables; + +// nft 1.1.4 changed behavior where the flag is printed as single string instead of array +// As such this lib should be able to parse both and return the same result. +// https://bugzilla.netfilter.org/show_bug.cgi?id=1806 +#[test] +fn test_parse_fib_flags() { + let file1 = BufReader::new( + File::open("resources/test/fixtures/single-fib-flag-1.json").expect("Cannot open file1"), + ); + let json1: Nftables = + serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_reader(file1)) + .expect("failed to parse json1"); + + let file2 = BufReader::new( + File::open("resources/test/fixtures/single-fib-flag-2.json").expect("Cannot open file2"), + ); + + let json2: Nftables = + serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_reader(file2)) + .expect("failed to parse json2"); + + assert_eq!(json1, json2, "Both parsed files should be identical") +}