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 4120E1FF348 for ; Wed, 17 Apr 2024 15:55:42 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 44A908C8C; Wed, 17 Apr 2024 15:54:48 +0200 (CEST) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Date: Wed, 17 Apr 2024 15:53:50 +0200 Message-Id: <20240417135404.573490-26-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240417135404.573490-1-s.hanreich@proxmox.com> References: <20240417135404.573490-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.316 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 v2 25/39] nftables: add libnftables bindings 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" Add a thin wrapper around libnftables, which can be used to run commands defined by the rust types. Reviewed-by: Lukas Wagner Reviewed-by: Max Carrara Co-authored-by: Wolfgang Bumiller Signed-off-by: Stefan Hanreich --- proxmox-nftables/src/context.rs | 243 ++++++++++++++++++++++++++++++++ proxmox-nftables/src/error.rs | 43 ++++++ proxmox-nftables/src/lib.rs | 3 + 3 files changed, 289 insertions(+) create mode 100644 proxmox-nftables/src/context.rs create mode 100644 proxmox-nftables/src/error.rs diff --git a/proxmox-nftables/src/context.rs b/proxmox-nftables/src/context.rs new file mode 100644 index 0000000..9ab51fb --- /dev/null +++ b/proxmox-nftables/src/context.rs @@ -0,0 +1,243 @@ +use std::ffi::CString; +use std::os::raw::{c_int, c_uint}; +use std::path::Path; + +use crate::command::{CommandOutput, Commands}; +use crate::error::NftError; + +#[rustfmt::skip] +pub mod debug { + use super::c_uint; + + pub const SCANNER : c_uint = 0x1; + pub const PARSER : c_uint = 0x2; + pub const EVALUATION : c_uint = 0x4; + pub const NETLINK : c_uint = 0x8; + pub const MNL : c_uint = 0x10; + pub const PROTO_CTX : c_uint = 0x20; + pub const SEGTREE : c_uint = 0x40; +} + +#[rustfmt::skip] +pub mod output { + use super::c_uint; + + pub const REVERSEDNS : c_uint = 1; + pub const SERVICE : c_uint = 1 << 1; + pub const STATELESS : c_uint = 1 << 2; + pub const HANDLE : c_uint = 1 << 3; + pub const JSON : c_uint = 1 << 4; + pub const ECHO : c_uint = 1 << 5; + pub const GUID : c_uint = 1 << 6; + pub const NUMERIC_PROTO : c_uint = 1 << 7; + pub const NUMERIC_PRIO : c_uint = 1 << 8; + pub const NUMERIC_SYMBOL : c_uint = 1 << 9; + pub const NUMERIC_TIME : c_uint = 1 << 10; + pub const TERSE : c_uint = 1 << 11; + + pub const NUMERIC_ALL : c_uint = NUMERIC_PROTO | NUMERIC_PRIO | NUMERIC_SYMBOL; +} + +#[link(name = "nftables")] +extern "C" { + fn nft_ctx_new(flags: u32) -> RawNftCtx; + fn nft_ctx_free(ctx: RawNftCtx); + + //fn nft_ctx_get_dry_run(ctx: RawNftCtx) -> bool; + fn nft_ctx_set_dry_run(ctx: RawNftCtx, dry: bool); + + fn nft_ctx_output_get_flags(ctx: RawNftCtx) -> c_uint; + fn nft_ctx_output_set_flags(ctx: RawNftCtx, flags: c_uint); + + // fn nft_ctx_output_get_debug(ctx: RawNftCtx) -> c_uint; + fn nft_ctx_output_set_debug(ctx: RawNftCtx, mask: c_uint); + + //fn nft_ctx_set_output(ctx: RawNftCtx, file: RawCFile) -> RawCFile; + fn nft_ctx_buffer_output(ctx: RawNftCtx) -> c_int; + fn nft_ctx_unbuffer_output(ctx: RawNftCtx) -> c_int; + fn nft_ctx_get_output_buffer(ctx: RawNftCtx) -> *const i8; + + fn nft_ctx_buffer_error(ctx: RawNftCtx) -> c_int; + fn nft_ctx_unbuffer_error(ctx: RawNftCtx) -> c_int; + fn nft_ctx_get_error_buffer(ctx: RawNftCtx) -> *const i8; + + fn nft_run_cmd_from_buffer(ctx: RawNftCtx, buf: *const i8) -> c_int; + fn nft_run_cmd_from_filename(ctx: RawNftCtx, filename: *const i8) -> c_int; +} + +#[derive(Clone, Copy)] +#[repr(transparent)] +struct RawNftCtx(*mut u8); + +pub struct NftCtx(RawNftCtx); + +impl Drop for NftCtx { + fn drop(&mut self) { + if !self.0 .0.is_null() { + unsafe { + nft_ctx_free(self.0); + } + } + } +} + +impl NftCtx { + pub fn new() -> Result { + let mut this = Self(unsafe { nft_ctx_new(0) }); + + if this.0 .0.is_null() { + return Err(NftError::msg("failed to instantiate nft context")); + } + + this.enable_json(); + + Ok(this) + } + + fn modify_flags(&mut self, func: impl FnOnce(c_uint) -> c_uint) { + unsafe { nft_ctx_output_set_flags(self.0, func(nft_ctx_output_get_flags(self.0))) } + } + + pub fn enable_debug(&mut self) { + unsafe { nft_ctx_output_set_debug(self.0, debug::PARSER | debug::SCANNER) } + } + + pub fn disable_debug(&mut self) { + unsafe { nft_ctx_output_set_debug(self.0, 0) } + } + + fn enable_json(&mut self) { + self.modify_flags(|flags| flags | output::JSON); + } + + pub fn set_dry_run(&mut self, on: bool) { + unsafe { nft_ctx_set_dry_run(self.0, on) } + } + + fn start_output_buffering(&mut self) -> Result<(), NftError> { + let rc = unsafe { nft_ctx_buffer_output(self.0) }; + NftError::expect_zero(rc, || "failed to start output buffering") + } + + fn stop_output_buffering(&mut self) { + let _ = unsafe { nft_ctx_unbuffer_output(self.0) }; + // ignore errors + } + + fn get_output_buffer(&mut self) -> Result { + let buf = unsafe { nft_ctx_get_output_buffer(self.0) }; + + if buf.is_null() { + return Err(NftError::msg("failed to get output buffer")); + } + + unsafe { std::ffi::CStr::from_ptr(buf) } + .to_str() + .map_err(NftError::msg) + .map(str::to_string) + } + + fn start_error_buffering(&mut self) -> Result<(), NftError> { + let rc = unsafe { nft_ctx_buffer_error(self.0) }; + NftError::expect_zero(rc, || "failed to start error buffering") + } + + fn stop_error_buffering(&mut self) { + let _ = unsafe { nft_ctx_unbuffer_error(self.0) }; + // ignore errors... + } + + fn get_error_buffer(&mut self) -> Result { + let buf = unsafe { nft_ctx_get_error_buffer(self.0) }; + + if buf.is_null() { + return Err(NftError::msg("failed to get error buffer")); + } + + unsafe { std::ffi::CStr::from_ptr(buf) } + .to_str() + .map_err(NftError::msg) + .map(str::to_string) + } + + fn start_buffering(&mut self) -> Result<(), NftError> { + self.start_output_buffering()?; + if let Err(err) = self.start_error_buffering() { + self.stop_output_buffering(); + return Err(err); + } + Ok(()) + } + + fn stop_buffering(&mut self) { + self.stop_error_buffering(); + self.stop_output_buffering(); + } + + fn buffered(&mut self, func: F) -> Result + where + E: From, + F: FnOnce(&mut Self) -> Result, + { + self.start_buffering()?; + let res = func(self); + self.stop_buffering(); + res + } + + fn current_error(&mut self) -> NftError { + match self.get_error_buffer() { + Ok(msg) => NftError(msg), + Err(err) => err, + } + } + + pub unsafe fn run_buffer(&mut self, buffer: &CString) -> Result { + self.buffered(|this| { + let rc = unsafe { nft_run_cmd_from_buffer(this.0, buffer.as_ptr()) }; + + if rc != 0 { + return Err(this.current_error()); + } + + this.get_output_buffer() + }) + } + + pub fn run_file>(&mut self, filename: P) -> Result { + use std::os::unix::ffi::OsStrExt; + + let filename = CString::new(filename.as_ref().as_os_str().as_bytes())?; + + self.buffered(move |this| { + let rc = unsafe { nft_run_cmd_from_filename(this.0, filename.as_ptr()) }; + if rc != 0 { + return Err(this.current_error()); + } + this.get_output_buffer() + }) + } + + pub fn run_nft_commands(&mut self, commands: &str) -> Result { + let string = CString::new(commands).map_err(NftError::msg)?; + + unsafe { self.run_buffer(&string) } + } + + pub fn run_commands(&mut self, commands: &Commands) -> Result, NftError> { + let json = + serde_json::to_vec(commands).expect("commands struct can be properly serialized"); + + let string = CString::new(json).expect("serialized json does not contain nul bytes"); + + let raw_output = unsafe { self.run_buffer(&string)? }; + + if raw_output.is_empty() { + return Ok(None); + } + + serde_json::from_str::(&raw_output) + .map(Option::from) + .map_err(NftError::msg) + } +} diff --git a/proxmox-nftables/src/error.rs b/proxmox-nftables/src/error.rs new file mode 100644 index 0000000..efdc13d --- /dev/null +++ b/proxmox-nftables/src/error.rs @@ -0,0 +1,43 @@ +use std::fmt; +use std::os::raw::c_int; + +#[derive(Debug)] +pub struct NftError(pub(crate) String); + +impl std::error::Error for NftError {} + +impl fmt::Display for NftError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "nftables error: {}", self.0) + } +} + +impl From for NftError { + fn from(err: serde_json::Error) -> Self { + Self::msg(err) + } +} + +impl NftError { + pub(crate) fn msg(msg: T) -> Self { + Self(msg.to_string()) + } + + pub(crate) fn expect_zero(rc: c_int, or_else: F) -> Result<(), NftError> + where + F: FnOnce() -> T, + T: fmt::Display, + { + if rc == 0 { + Ok(()) + } else { + Err(Self(or_else().to_string())) + } + } +} + +impl From for NftError { + fn from(err: std::ffi::NulError) -> Self { + Self::msg(err) + } +} diff --git a/proxmox-nftables/src/lib.rs b/proxmox-nftables/src/lib.rs index 60ddb3f..61a6665 100644 --- a/proxmox-nftables/src/lib.rs +++ b/proxmox-nftables/src/lib.rs @@ -1,9 +1,12 @@ pub mod command; +pub mod context; +pub mod error; pub mod expression; pub mod helper; pub mod statement; pub mod types; pub use command::Command; +pub use context::NftCtx; pub use expression::Expression; pub use statement::Statement; -- 2.39.2 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel