From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id DC65F1FF37F for ; Thu, 18 Apr 2024 18:17:12 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4FA06316C8; Thu, 18 Apr 2024 18:15:21 +0200 (CEST) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Date: Thu, 18 Apr 2024 18:14:16 +0200 Message-Id: <20240418161434.709473-22-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240418161434.709473-1-s.hanreich@proxmox.com> References: <20240418161434.709473-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.289 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 v3 21/39] nftables: statement: 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: , Reply-To: Proxmox VE development discussion Cc: Wolfgang Bumiller Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" Adds an enum containing most of the statements defined in the nftables-json schema [1]. [1] https://manpages.debian.org/bookworm/libnftables1/libnftables-json.5.en.html#STATEMENTS Reviewed-by: Lukas Wagner Reviewed-by: Max Carrara Co-authored-by: Wolfgang Bumiller Signed-off-by: Stefan Hanreich --- proxmox-nftables/Cargo.toml | 2 + proxmox-nftables/src/lib.rs | 2 + proxmox-nftables/src/statement.rs | 321 ++++++++++++++++++++++++++++++ proxmox-nftables/src/types.rs | 18 +- 4 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 proxmox-nftables/src/statement.rs diff --git a/proxmox-nftables/Cargo.toml b/proxmox-nftables/Cargo.toml index 7e607e8..e84509d 100644 --- a/proxmox-nftables/Cargo.toml +++ b/proxmox-nftables/Cargo.toml @@ -15,6 +15,8 @@ config-ext = ["dep:proxmox-ve-config"] [dependencies] log = "0.4" +anyhow = "1" +thiserror = "1" serde = { version = "1", features = [ "derive" ] } serde_json = "1" diff --git a/proxmox-nftables/src/lib.rs b/proxmox-nftables/src/lib.rs index 712858b..40f6bab 100644 --- a/proxmox-nftables/src/lib.rs +++ b/proxmox-nftables/src/lib.rs @@ -1,5 +1,7 @@ pub mod expression; pub mod helper; +pub mod statement; pub mod types; pub use expression::Expression; +pub use statement::Statement; diff --git a/proxmox-nftables/src/statement.rs b/proxmox-nftables/src/statement.rs new file mode 100644 index 0000000..e6371f6 --- /dev/null +++ b/proxmox-nftables/src/statement.rs @@ -0,0 +1,321 @@ +use anyhow::{bail, Error}; +use serde::{Deserialize, Serialize}; + +use crate::expression::Meta; +use crate::helper::{NfVec, Null}; +use crate::types::{RateTimescale, RateUnit, Verdict}; +use crate::Expression; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Statement { + Match(Match), + Mangle(Mangle), + Limit(Limit), + Notrack(Null), + Reject(Reject), + Set(Set), + Log(Log), + #[serde(rename = "ct helper")] + CtHelper(String), + Vmap(Vmap), + Comment(String), + + #[serde(untagged)] + Verdict(Verdict), +} + +impl Statement { + pub const fn make_accept() -> Self { + Statement::Verdict(Verdict::Accept(Null)) + } + + pub const fn make_drop() -> Self { + Statement::Verdict(Verdict::Drop(Null)) + } + + pub const fn make_return() -> Self { + Statement::Verdict(Verdict::Return(Null)) + } + + pub const fn make_continue() -> Self { + Statement::Verdict(Verdict::Continue(Null)) + } + + pub fn jump(target: impl Into) -> Self { + Statement::Verdict(Verdict::Jump { + target: target.into(), + }) + } + + pub fn goto(target: impl Into) -> Self { + Statement::Verdict(Verdict::Goto { + target: target.into(), + }) + } +} + +impl From for Statement { + #[inline] + fn from(m: Match) -> Statement { + Statement::Match(m) + } +} + +impl From for Statement { + #[inline] + fn from(m: Mangle) -> Statement { + Statement::Mangle(m) + } +} + +impl From for Statement { + #[inline] + fn from(m: Reject) -> Statement { + Statement::Reject(m) + } +} + +impl From for Statement { + #[inline] + fn from(m: Set) -> Statement { + Statement::Set(m) + } +} + +impl From for Statement { + #[inline] + fn from(m: Vmap) -> Statement { + Statement::Vmap(m) + } +} + +impl From for Statement { + #[inline] + fn from(log: Log) -> Statement { + Statement::Log(log) + } +} + +impl> From for Statement { + #[inline] + fn from(limit: T) -> Statement { + Statement::Limit(limit.into()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum RejectType { + #[serde(rename = "tcp reset")] + TcpRst, + IcmpX, + Icmp, + IcmpV6, +} + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct Reject { + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + ty: Option, + #[serde(skip_serializing_if = "Option::is_none")] + expr: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct Log { + #[serde(skip_serializing_if = "Option::is_none")] + prefix: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + group: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + snaplen: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + queue_threshold: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + level: Option, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + flags: NfVec, +} + +impl Log { + pub fn new_nflog(prefix: String, group: i64) -> Self { + Self { + prefix: Some(prefix), + group: Some(group), + ..Default::default() + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + Emerg, + Alert, + Crit, + Err, + Warn, + Notice, + Info, + Debug, + Audit, +} + +impl LogLevel { + pub fn nflog_level(&self) -> u8 { + match self { + LogLevel::Emerg => 0, + LogLevel::Alert => 1, + LogLevel::Crit => 2, + LogLevel::Err => 3, + LogLevel::Warn => 4, + LogLevel::Notice => 5, + LogLevel::Info => 6, + LogLevel::Debug => 7, + LogLevel::Audit => 7, + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum LogFlag { + #[serde(rename = "tcp sequence")] + TcpSequence, + #[serde(rename = "tcp options")] + TcpOptions, + #[serde(rename = "ip options")] + IpOptions, + + Skuid, + Ether, + All, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Limit { + Named(String), + Anonymous(AnonymousLimit), +} + +impl> From for Limit { + fn from(value: T) -> Self { + Limit::Anonymous(value.into()) + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, Default)] +pub struct AnonymousLimit { + pub rate: i64, + + #[serde(skip_serializing_if = "Option::is_none")] + pub rate_unit: Option, + + pub per: RateTimescale, + + #[serde(skip_serializing_if = "Option::is_none")] + pub burst: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub burst_unit: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub inv: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Vmap { + key: Expression, + data: Expression, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Match { + op: Operator, + left: Expression, + right: Expression, +} + +impl Match { + pub fn new(op: Operator, left: impl Into, right: impl Into) -> Self { + Self { + op, + left: left.into(), + right: right.into(), + } + } + + pub fn new_eq(left: impl Into, right: impl Into) -> Self { + Self::new(Operator::Eq, left, right) + } + + pub fn new_ne(left: impl Into, right: impl Into) -> Self { + Self::new(Operator::Ne, left, right) + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub enum Operator { + #[serde(rename = "&")] + And, + #[serde(rename = "|")] + Or, + #[serde(rename = "^")] + Xor, + #[serde(rename = "<<")] + ShiftLeft, + #[serde(rename = ">>")] + ShiftRight, + #[serde(rename = "==")] + Eq, + #[serde(rename = "!=")] + Ne, + #[serde(rename = "<")] + Lt, + #[serde(rename = ">")] + Gt, + #[serde(rename = "<=")] + Le, + #[serde(rename = ">=")] + Ge, + #[serde(rename = "in")] + In, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Mangle { + pub key: Expression, + pub value: Expression, +} + +impl Mangle { + pub fn set_mark(value: impl Into) -> Self { + Self { + key: Meta::new("mark").into(), + value: value.into(), + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum SetOperation { + Add, + Update, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Set { + pub op: SetOperation, + pub elem: Expression, + pub set: String, + pub stmt: Option>, +} diff --git a/proxmox-nftables/src/types.rs b/proxmox-nftables/src/types.rs index 942c866..a8ec599 100644 --- a/proxmox-nftables/src/types.rs +++ b/proxmox-nftables/src/types.rs @@ -30,6 +30,23 @@ impl Display for Verdict { } } +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub enum RateUnit { + Packets, + Bytes, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, Default)] +#[cfg_attr(test, derive(Eq, PartialEq))] +#[serde(rename_all = "lowercase")] +pub enum RateTimescale { + #[default] + Second, + Minute, + Hour, + Day, +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ElemConfig { timeout: Option, @@ -50,4 +67,3 @@ impl ElemConfig { } } } - -- 2.39.2 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel