From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id EEAE390940 for ; Tue, 2 Apr 2024 19:16:44 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 64D78A806 for ; Tue, 2 Apr 2024 19:16:40 +0200 (CEST) Received: from lana.proxmox.com (unknown [94.136.29.99]) by firstgate.proxmox.com (Proxmox) with ESMTP for ; Tue, 2 Apr 2024 19:16:37 +0200 (CEST) Received: by lana.proxmox.com (Postfix, from userid 10043) id 588D92C35BC; Tue, 2 Apr 2024 19:16:31 +0200 (CEST) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Cc: Stefan Hanreich , Wolfgang Bumiller Date: Tue, 2 Apr 2024 19:16:10 +0200 Message-Id: <20240402171629.536804-19-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240402171629.536804-1-s.hanreich@proxmox.com> References: <20240402171629.536804-1-s.hanreich@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.329 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Subject: [pve-devel] [PATCH proxmox-firewall 18/37] nftables: add helpers X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 02 Apr 2024 17:16:45 -0000 Several objects, statements and expressions in nftables-json require null values, for instance: { "flush": { "ruleset": null }} For this purpose we define our own Null type, which we can then easily use for defining types that accept Null as value. Several keys accept as value either a singular element (string or object) if there is only one object, but an array if there are multiple objects. For instance when adding a single element to a set: { "element": { ... "elem": "element1" }} but when adding multiple elements: { "element": { ... "elem": ["element1", "element2"] }} NfVec is a wrapper for Vec that serializes into T iff Vec contains one element, otherwise it serializes like a Vec would normally do. Co-authored-by: Wolfgang Bumiller Signed-off-by: Stefan Hanreich --- proxmox-nftables/Cargo.toml | 4 + proxmox-nftables/src/helper.rs | 190 +++++++++++++++++++++++++++++++++ proxmox-nftables/src/lib.rs | 1 + 3 files changed, 195 insertions(+) create mode 100644 proxmox-nftables/src/helper.rs diff --git a/proxmox-nftables/Cargo.toml b/proxmox-nftables/Cargo.toml index 764e231..ebece9d 100644 --- a/proxmox-nftables/Cargo.toml +++ b/proxmox-nftables/Cargo.toml @@ -13,4 +13,8 @@ license = "AGPL-3" [dependencies] log = "0.4" +serde = { version = "1", features = [ "derive" ] } +serde_json = "1" +serde_plain = "1" + proxmox-ve-config = { path = "../proxmox-ve-config", optional = true } diff --git a/proxmox-nftables/src/helper.rs b/proxmox-nftables/src/helper.rs new file mode 100644 index 0000000..77ce347 --- /dev/null +++ b/proxmox-nftables/src/helper.rs @@ -0,0 +1,190 @@ +use std::fmt; +use std::marker::PhantomData; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Debug)] +pub struct Null; + +impl<'de> Deserialize<'de> for Null { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + + match Option::<()>::deserialize(deserializer)? { + None => Ok(Self), + Some(_) => Err(D::Error::custom("expected null")), + } + } +} + +impl Serialize for Null { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_none() + } +} + +impl fmt::Display for Null { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("null") + } +} + +#[derive(Clone, Debug)] +pub struct NfVec(pub(crate) Vec); + +impl Default for NfVec { + fn default() -> Self { + Self::new() + } +} + +impl NfVec { + pub const fn new() -> Self { + Self(Vec::new()) + } + + pub fn one(value: T) -> Self { + Self(vec![value]) + } +} + +impl From> for NfVec { + fn from(v: Vec) -> Self { + Self(v) + } +} + +impl From> for Vec { + fn from(v: NfVec) -> Self { + v.0 + } +} + +impl FromIterator for NfVec { + fn from_iter>(iter: I) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl std::ops::Deref for NfVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for NfVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Serialize for NfVec { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if self.len() == 1 { + self[0].serialize(serializer) + } else { + self.0.serialize(serializer) + } + } +} + +macro_rules! visit_value { + ($( ($visit:ident, $($ty:tt)+), )+) => { + $( + fn $visit(self, value: $($ty)+) -> Result + where + E: Error, + { + T::deserialize(value.into_deserializer()).map(NfVec::one) + } + )+ + }; +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for NfVec { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::{Error, IntoDeserializer}; + + struct V(PhantomData); + + impl<'de, T: Deserialize<'de>> serde::de::Visitor<'de> for V { + type Value = NfVec; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("an array or single element") + } + + fn visit_seq(self, seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + Vec::::deserialize(serde::de::value::SeqAccessDeserializer::new(seq)).map(NfVec) + } + + fn visit_map(self, map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + T::deserialize(serde::de::value::MapAccessDeserializer::new(map)).map(NfVec::one) + } + + fn visit_none(self) -> Result + where + E: Error, + { + Ok(NfVec::new()) + } + + fn visit_unit(self) -> Result + where + E: Error, + { + Ok(NfVec::new()) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + visit_value! { + (visit_bool, bool), + (visit_borrowed_bytes, &'de [u8]), + (visit_borrowed_str, &'de str), + (visit_byte_buf, Vec), + (visit_bytes, &[u8]), + (visit_char, char), + (visit_f32, f32), + (visit_f64, f64), + (visit_i8, i8), + (visit_i16, i16), + (visit_i32, i32), + (visit_i64, i64), + (visit_u8, u8), + (visit_u16, u16), + (visit_u32, u32), + (visit_u64, u64), + (visit_str, &str), + (visit_string, String), + } + } + + deserializer.deserialize_any(V::(PhantomData)) + } +} diff --git a/proxmox-nftables/src/lib.rs b/proxmox-nftables/src/lib.rs index e69de29..485bb81 100644 --- a/proxmox-nftables/src/lib.rs +++ b/proxmox-nftables/src/lib.rs @@ -0,0 +1 @@ +pub mod helper; -- 2.39.2