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 9BA0990A57 for ; Tue, 2 Apr 2024 19:17:03 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 21CA2A843 for ; Tue, 2 Apr 2024 19:16:46 +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:39 +0200 (CEST) Received: by lana.proxmox.com (Postfix, from userid 10043) id 8A0E82C3797; 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:15 +0200 Message-Id: <20240402171629.536804-24-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.315 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 23/37] nftables: commands: add types 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:17:03 -0000 Add rust types for most of the nftables commands as defined by libnftables-json [1]. Different commands require different keys to be set for the same type of object. E.g. deleting an object usually only requires a name + name of the container (table/chain/rule). Creating an object usually requires a few more keys, depending on the type of object created. In order to be able to model the different objects for the different commands, I've created specific models for a command where necessary. Parts that are common across multiple commands (e.g. names) have been moved to their own structs, so they can be reused. [1] https://manpages.debian.org/bookworm/libnftables1/libnftables-json.5.en.html#COMMAND_OBJECTS Co-authored-by: Wolfgang Bumiller Signed-off-by: Stefan Hanreich --- proxmox-nftables/src/command.rs | 221 ++++++++++ proxmox-nftables/src/lib.rs | 2 + proxmox-nftables/src/types.rs | 755 +++++++++++++++++++++++++++++++- 3 files changed, 977 insertions(+), 1 deletion(-) create mode 100644 proxmox-nftables/src/command.rs diff --git a/proxmox-nftables/src/command.rs b/proxmox-nftables/src/command.rs new file mode 100644 index 0000000..59163bc --- /dev/null +++ b/proxmox-nftables/src/command.rs @@ -0,0 +1,221 @@ +use std::ops::{Deref, DerefMut}; + +use crate::helper::Null; +use crate::types::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct Commands { + nftables: Vec, +} + +impl Commands { + pub fn new(commands: Vec) -> Self { + Self { nftables: commands } + } +} + +impl Deref for Commands { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.nftables + } +} + +impl DerefMut for Commands { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.nftables + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Command { + Add(Add), + Create(Add), + Delete(Delete), + Flush(Flush), + List(List), + // Insert(super::Rule), + // Rename(RenameChain), + // Replace(super::Rule), +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum List { + Chains(Null), +} + +impl List { + #[inline] + pub fn chains() -> Command { + Command::List(List::Chains(Null)) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Add { + Table(AddTable), + Chain(AddChain), + Rule(AddRule), + Set(AddSet), + Map(AddMap), + Limit(AddLimit), + Element(AddElement), + #[serde(rename = "ct helper")] + CtHelper(AddCtHelper), +} + +impl Add { + #[inline] + pub fn table(table: impl Into) -> Command { + Command::Add(Add::Table(table.into())) + } + + #[inline] + pub fn chain(chain: impl Into) -> Command { + Command::Add(Add::Chain(chain.into())) + } + + #[inline] + pub fn rule(rule: impl Into) -> Command { + Command::Add(Add::Rule(rule.into())) + } + + #[inline] + pub fn set(set: impl Into) -> Command { + Command::Add(Add::Set(set.into())) + } + + #[inline] + pub fn map(map: impl Into) -> Command { + Command::Add(Add::Map(map.into())) + } + + #[inline] + pub fn limit(limit: impl Into) -> Command { + Command::Add(Add::Limit(limit.into())) + } + + #[inline] + pub fn element(element: impl Into) -> Command { + Command::Add(Add::Element(element.into())) + } + + #[inline] + pub fn ct_helper(ct_helper: impl Into) -> Command { + Command::Add(Add::CtHelper(ct_helper.into())) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Flush { + Table(TableName), + Chain(ChainName), + Set(SetName), + Map(SetName), + Ruleset(Null), +} + +impl Flush { + #[inline] + pub fn table(table: impl Into) -> Command { + Command::Flush(Flush::Table(table.into())) + } + + #[inline] + pub fn chain(chain: impl Into) -> Command { + Command::Flush(Flush::Chain(chain.into())) + } + + #[inline] + pub fn set(set: impl Into) -> Command { + Command::Flush(Flush::Set(set.into())) + } + + #[inline] + pub fn map(map: impl Into) -> Command { + Command::Flush(Flush::Map(map.into())) + } + + #[inline] + pub fn ruleset() -> Command { + Command::Flush(Flush::Ruleset(Null)) + } +} + +impl From for Flush { + #[inline] + fn from(value: TableName) -> Self { + Flush::Table(value) + } +} + +impl From for Flush { + #[inline] + fn from(value: ChainName) -> Self { + Flush::Chain(value) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Delete { + Table(TableName), + Chain(ChainName), +} + +impl Delete { + #[inline] + pub fn table(table: impl Into) -> Command { + Command::Delete(Delete::Table(table.into())) + } + + #[inline] + pub fn chain(chain: impl Into) -> Command { + Command::Delete(Delete::Chain(chain.into())) + } +} + +impl From for Delete { + #[inline] + fn from(value: TableName) -> Self { + Delete::Table(value) + } +} + +impl From for Delete { + #[inline] + fn from(value: ChainName) -> Self { + Delete::Chain(value) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum ListOutput { + Metainfo(serde_json::Value), + // Table(super::AddTable), + Chain(ListChain), + // Rule(super::Rule), + // Set(super::Set), + // Map(super::Map), + // Element(super::SetElement), +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CommandOutput { + pub nftables: Vec, +} + +impl Deref for CommandOutput { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.nftables + } +} diff --git a/proxmox-nftables/src/lib.rs b/proxmox-nftables/src/lib.rs index 40f6bab..60ddb3f 100644 --- a/proxmox-nftables/src/lib.rs +++ b/proxmox-nftables/src/lib.rs @@ -1,7 +1,9 @@ +pub mod command; pub mod expression; pub mod helper; pub mod statement; pub mod types; +pub use command::Command; pub use expression::Expression; pub use statement::Statement; diff --git a/proxmox-nftables/src/types.rs b/proxmox-nftables/src/types.rs index b99747b..f9dc9b6 100644 --- a/proxmox-nftables/src/types.rs +++ b/proxmox-nftables/src/types.rs @@ -1,8 +1,90 @@ use std::fmt::Display; +use std::ops::{Deref, DerefMut}; + +use crate::expression::IpFamily; +use crate::helper::{NfVec, Null}; +use crate::{Expression, Statement}; use serde::{Deserialize, Serialize}; -use crate::helper::Null; + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +pub struct Handle(i32); + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum TableFamily { + Ip, + Ip6, + Inet, + Arp, + Bridge, + Netdev, +} +serde_plain::derive_display_from_serialize!(TableFamily); + +impl TableFamily { + pub fn ip_families(&self) -> Vec { + match self { + TableFamily::Ip => vec![IpFamily::Ip], + TableFamily::Ip6 => vec![IpFamily::Ip6], + _ => vec![IpFamily::Ip, IpFamily::Ip6], + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum ElementType { + Ifname, + Ipv4Addr, + Ipv6Addr, +} +serde_plain::derive_display_from_serialize!(ElementType); + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum ChainType { + Filter, + Nat, + Route, +} +serde_plain::derive_display_from_serialize!(ChainType); + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum SetPolicy { + Performance, + Memory, +} +serde_plain::derive_display_from_serialize!(SetPolicy); + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum SetFlag { + Constant, + Interval, + Timeout, +} +serde_plain::derive_display_from_serialize!(SetFlag); + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum OutputType { + Verdict, + Type(ElementType), +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Hook { + Prerouting, + Input, + Forward, + Output, + Postrouting, +} +serde_plain::derive_display_from_serialize!(Hook); #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] @@ -30,6 +112,32 @@ impl Display for Verdict { } } +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum ChainPolicy { + Accept, + Drop, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum PriorityKeyword { + Raw, + Mangle, + DstNat, + Filter, + Security, + SrcNat, + Out, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Priority { + Keyword(PriorityKeyword), + Number(i64), +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub enum RateUnit { Packets, @@ -47,6 +155,529 @@ pub enum RateTimescale { Day, } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TableName { + family: TableFamily, + name: String, +} + +impl TableName { + pub fn new(family: TableFamily, name: impl Into) -> Self { + Self { + family, + name: name.into(), + } + } + + pub fn family(&self) -> &TableFamily { + &self.family + } + + pub fn name(&self) -> &str { + &self.name + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TablePart { + family: TableFamily, + table: String, +} + +impl TablePart { + pub fn new(family: TableFamily, name: impl Into) -> Self { + Self { + family, + table: name.into(), + } + } + + pub fn family(&self) -> &TableFamily { + &self.family + } + + pub fn table(&self) -> &str { + &self.table + } +} + +impl From for TableName { + fn from(t: TablePart) -> Self { + Self { + family: t.family, + name: t.table, + } + } +} + +impl From for TablePart { + fn from(t: TableName) -> Self { + Self { + family: t.family, + table: t.name, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ChainName { + #[serde(flatten)] + table: TablePart, + name: String, +} + +impl From for ChainName { + fn from(value: AddChain) -> Self { + Self { + table: value.table, + name: value.name, + } + } +} + +impl From for ChainName { + fn from(value: ListChain) -> Self { + Self { + table: value.table, + name: value.name, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ChainPart { + #[serde(flatten)] + table: TablePart, + chain: String, +} + +impl ChainPart { + pub fn new(table: TablePart, chain: impl Into) -> Self { + Self { + table, + chain: chain.into(), + } + } + + pub fn table(&self) -> &TablePart { + &self.table + } + + pub fn name(&self) -> &str { + &self.chain + } +} + +impl From for ChainPart { + fn from(c: ChainName) -> Self { + Self { + table: c.table, + chain: c.name, + } + } +} + +impl From for ChainName { + fn from(c: ChainPart) -> Self { + Self { + table: c.table, + name: c.chain, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AddTable { + family: TableFamily, + name: String, + + #[serde(skip_serializing_if = "Option::is_none")] + handle: Option, +} + +impl AddTable { + pub fn new(family: TableFamily, name: impl Into) -> Self { + Self { + family, + name: name.into(), + handle: None, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct BaseChainConfig { + #[serde(rename = "type")] + ty: ChainType, + hook: Hook, + prio: Expression, + policy: ChainPolicy, + + /// netdev family only + #[serde(skip_serializing_if = "Option::is_none")] + dev: Option, +} + +impl BaseChainConfig { + pub fn new( + ty: ChainType, + hook: Hook, + prio: impl Into, + policy: ChainPolicy, + ) -> Self { + Self { + ty, + hook, + prio: prio.into(), + policy, + dev: None, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AddChain { + #[serde(flatten)] + table: TablePart, + name: String, + + #[serde(flatten, skip_serializing_if = "Option::is_none")] + config: Option, +} + +impl AddChain { + pub fn new(table: TablePart, name: impl Into) -> Self { + Self { + table, + name: name.into(), + config: None, + } + } + + pub fn new_base_chain( + table: TablePart, + name: impl Into, + config: BaseChainConfig, + ) -> Self { + Self { + table, + name: name.into(), + config: Some(config), + } + } +} + +impl From for AddChain { + #[inline] + fn from(part: ChainPart) -> Self { + Self::new(part.table, part.chain) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AddRule { + #[serde(flatten)] + chain: ChainPart, + + #[serde(skip_serializing_if = "Option::is_none")] + handle: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + index: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + comment: Option, + + expr: Vec, +} + +impl Deref for AddRule { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.expr + } +} + +impl DerefMut for AddRule { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.expr + } +} + +impl AddRule { + pub fn from_statement(chain: ChainPart, expression: impl Into) -> Self { + Self { + chain, + expr: vec![expression.into()], + handle: None, + index: None, + comment: None, + } + } + + pub fn from_statements>( + chain: ChainPart, + expression: I, + ) -> Self { + Self { + chain, + expr: expression.into_iter().collect(), + handle: None, + index: None, + comment: None, + } + } + + pub fn new(chain: ChainPart) -> Self { + Self { + chain, + expr: Vec::new(), + handle: None, + index: None, + comment: None, + } + } + + pub fn with_comment(mut self, comment: impl Into) -> Self { + self.comment = Some(comment.into()); + self + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct SetConfig { + #[serde(flatten)] + name: SetName, + + #[serde(rename = "type", default, skip_serializing_if = "Vec::is_empty")] + ty: NfVec, + + #[serde(skip_serializing_if = "Option::is_none")] + policy: Option, + + #[serde(skip_serializing_if = "Vec::is_empty", default)] + flags: Vec, + + #[serde(skip_serializing_if = "Option::is_none")] + timeout: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + gc_interval: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + size: Option, +} + +impl SetConfig { + pub fn new(name: impl Into, ty: impl Into>) -> Self { + Self { + name: name.into(), + ty: ty.into(), + flags: Vec::new(), + policy: None, + timeout: None, + gc_interval: None, + size: None, + } + } + + pub fn name(&self) -> &SetName { + &self.name + } + + pub fn with_flag(mut self, flag: SetFlag) -> Self { + self.flags.push(flag); + self + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct AddMap { + #[serde(flatten)] + config: SetConfig, + + map: OutputType, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + elem: NfVec, +} + +impl AddMap { + pub fn new(config: SetConfig, output_type: OutputType) -> Self { + Self { + config, + map: output_type, + elem: NfVec::new(), + } + } +} + +impl Deref for AddMap { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.elem + } +} + +impl DerefMut for AddMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.elem + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AddSet { + #[serde(flatten)] + config: SetConfig, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + elem: NfVec, +} + +impl From for AddSet { + fn from(value: SetConfig) -> Self { + Self { + config: value, + elem: NfVec::new(), + } + } +} + +impl Deref for AddSet { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.elem + } +} + +impl DerefMut for AddSet { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.elem + } +} + +impl AddSet { + pub fn new(config: impl Into, elements: impl IntoIterator) -> Self { + Self { + config: config.into(), + elem: NfVec::from(elements.into_iter().collect::>()), + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct SetName { + #[serde(flatten)] + table: TablePart, + name: String, +} + +impl SetName { + pub fn new(table: TablePart, name: impl Into) -> Self { + Self { + table, + name: name.into(), + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct SetElem(Expression); + +impl From for SetElem { + #[inline] + fn from(value: Expression) -> Self { + Self(value) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum MapValue { + Expression(Expression), + Verdict(Verdict), + // Concat +} + +impl From for MapValue { + #[inline] + fn from(value: Verdict) -> Self { + Self::Verdict(value) + } +} + +impl From for MapValue { + #[inline] + fn from(value: Expression) -> Self { + Self::Expression(value) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct MapElem((Expression, MapValue)); + +impl MapElem { + pub fn new(key: Expression, value: impl Into) -> Self { + Self((key, value.into())) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AddSetElement { + #[serde(flatten)] + set: SetName, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + elem: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct AddMapElement { + #[serde(flatten)] + map: SetName, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + elem: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum AddElement { + Set(AddSetElement), + Map(AddMapElement), +} + +impl AddElement { + pub fn map_from_expressions( + map: SetName, + elem: impl IntoIterator, + ) -> Self { + Self::Map(AddMapElement { + map, + elem: Vec::from_iter( + elem.into_iter() + .map(|(key, value)| MapElem::new(key, value).into()), + ), + }) + } + + pub fn set_from_expressions(set: SetName, elem: impl IntoIterator) -> Self { + Self::Set(AddSetElement { + set, + elem: Vec::from_iter(elem.into_iter().map(SetElement::from)), + }) + } +} + +impl From for AddElement { + fn from(value: AddSetElement) -> Self { + AddElement::Set(value) + } +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ElemConfig { timeout: Option, @@ -68,3 +699,125 @@ impl ElemConfig { } } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct SetElemObject { + #[serde(flatten)] + config: ElemConfig, + elem: SetElem, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct MapElemObject { + #[serde(flatten)] + config: ElemConfig, + elem: MapElem, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum MapElement { + #[serde(rename = "elem")] + Object(MapElemObject), + #[serde(untagged)] + Value(MapElem), +} + +impl From for MapElement { + fn from(value: MapElem) -> Self { + Self::Value(value) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum SetElement { + #[serde(rename = "elem")] + Object(SetElemObject), + #[serde(untagged)] + Value(SetElem), +} + +impl From for SetElement { + #[inline] + fn from(value: Expression) -> Self { + Self::Value(SetElem::from(value)) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub struct AddLimit { + #[serde(flatten)] + table: TablePart, + + name: String, + + rate: i64, + + #[serde(skip_serializing_if = "Option::is_none")] + unit: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + per: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + burst: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + inv: Option, +} + +impl AddLimit { + pub fn new(table: TablePart, name: String, rate: i64) -> Self { + Self { + table, + name, + rate, + unit: None, + per: None, + burst: None, + inv: None, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum L3Protocol { + Ip, + Ip6, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum CtHelperProtocol { + TCP, + UDP, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename = "ct helper")] +pub struct AddCtHelper { + #[serde(flatten)] + pub table: TablePart, + pub name: String, + #[serde(rename = "type")] + pub ty: String, + pub protocol: CtHelperProtocol, + pub l3proto: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ListChain { + #[serde(flatten)] + table: TablePart, + name: String, + handle: i64, + + #[serde(flatten)] + config: Option, +} + +impl ListChain { + pub fn name(&self) -> &str { + &self.name + } +} -- 2.39.2