public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Stefan Hanreich <s.hanreich@proxmox.com>
To: pve-devel@lists.proxmox.com
Cc: Stefan Hanreich <s.hanreich@proxmox.com>,
	Wolfgang Bumiller <w.bumiller@proxmox.com>
Subject: [pve-devel] [PATCH proxmox-firewall 25/37] nftables: add libnftables bindings
Date: Tue,  2 Apr 2024 19:16:17 +0200	[thread overview]
Message-ID: <20240402171629.536804-26-s.hanreich@proxmox.com> (raw)
In-Reply-To: <20240402171629.536804-1-s.hanreich@proxmox.com>

Add a thin wrapper around libnftables, which can be used to run
commands defined by the rust types.

Co-authored-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 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<Self, NftError> {
+        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<String, NftError> {
+        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<String, NftError> {
+        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<F, R, E>(&mut self, func: F) -> Result<R, E>
+    where
+        E: From<NftError>,
+        F: FnOnce(&mut Self) -> Result<R, E>,
+    {
+        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<String, NftError> {
+        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<P: AsRef<Path>>(&mut self, filename: P) -> Result<String, NftError> {
+        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<String, NftError> {
+        let string = CString::new(commands).map_err(NftError::msg)?;
+
+        unsafe { self.run_buffer(&string) }
+    }
+
+    pub fn run_commands(&mut self, commands: &Commands) -> Result<Option<CommandOutput>, 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::<CommandOutput>(&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<serde_json::Error> for NftError {
+    fn from(err: serde_json::Error) -> Self {
+        Self::msg(err)
+    }
+}
+
+impl NftError {
+    pub(crate) fn msg<T: fmt::Display>(msg: T) -> Self {
+        Self(msg.to_string())
+    }
+
+    pub(crate) fn expect_zero<F, T>(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<std::ffi::NulError> 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




  parent reply	other threads:[~2024-04-02 17:26 UTC|newest]

Thread overview: 67+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-02 17:15 [pve-devel] [RFC container/firewall/manager/proxmox-firewall/qemu-server 00/37] proxmox firewall nftables implementation Stefan Hanreich
2024-04-02 17:15 ` [pve-devel] [PATCH proxmox-firewall 01/37] config: add proxmox-ve-config crate Stefan Hanreich
2024-04-02 17:15 ` [pve-devel] [PATCH proxmox-firewall 02/37] config: firewall: add types for ip addresses Stefan Hanreich
2024-04-03 10:46   ` Max Carrara
2024-04-09  8:26     ` Stefan Hanreich
2024-04-02 17:15 ` [pve-devel] [PATCH proxmox-firewall 03/37] config: firewall: add types for ports Stefan Hanreich
2024-04-02 17:15 ` [pve-devel] [PATCH proxmox-firewall 04/37] config: firewall: add types for log level and rate limit Stefan Hanreich
2024-04-02 17:15 ` [pve-devel] [PATCH proxmox-firewall 05/37] config: firewall: add types for aliases Stefan Hanreich
2024-04-02 17:15 ` [pve-devel] [PATCH proxmox-firewall 06/37] config: host: add helpers for host network configuration Stefan Hanreich
2024-04-03 10:46   ` Max Carrara
2024-04-09  8:32     ` Stefan Hanreich
2024-04-09 14:20   ` Lukas Wagner
2024-04-02 17:15 ` [pve-devel] [PATCH proxmox-firewall 07/37] config: guest: add helpers for parsing guest network config Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 08/37] config: firewall: add types for ipsets Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 09/37] config: firewall: add types for rules Stefan Hanreich
2024-04-03 10:46   ` Max Carrara
2024-04-09  8:36     ` Stefan Hanreich
2024-04-09 14:55     ` Lukas Wagner
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 10/37] config: firewall: add types for security groups Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 11/37] config: firewall: add generic parser for firewall configs Stefan Hanreich
2024-04-03 10:47   ` Max Carrara
2024-04-09  8:38     ` Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 12/37] config: firewall: add cluster-specific config + option types Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 13/37] config: firewall: add host specific " Stefan Hanreich
2024-04-03 10:47   ` Max Carrara
2024-04-09  8:55     ` Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 14/37] config: firewall: add guest-specific " Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 15/37] config: firewall: add firewall macros Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 16/37] config: firewall: add conntrack helper types Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 17/37] nftables: add crate for libnftables bindings Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 18/37] nftables: add helpers Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 19/37] nftables: expression: add types Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 20/37] nftables: expression: implement conversion traits for firewall config Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 21/37] nftables: statement: add types Stefan Hanreich
2024-04-03 10:47   ` Max Carrara
2024-04-09  8:58     ` Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 22/37] nftables: statement: add conversion traits for config types Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 23/37] nftables: commands: add types Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 24/37] nftables: types: add conversion traits Stefan Hanreich
2024-04-02 17:16 ` Stefan Hanreich [this message]
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 26/37] firewall: add firewall crate Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 27/37] firewall: add base ruleset Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 28/37] firewall: add config loader Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 29/37] firewall: add rule generation logic Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 30/37] firewall: add object " Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 31/37] firewall: add ruleset " Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 32/37] firewall: add proxmox-firewall binary Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH proxmox-firewall 33/37] firewall: add files for debian packaging Stefan Hanreich
2024-04-03 13:14   ` Fabian Grünbichler
2024-04-09  8:56     ` Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH qemu-server 34/37] firewall: add handling for new nft firewall Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH pve-container 35/37] " Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH pve-firewall 36/37] add configuration option for new nftables firewall Stefan Hanreich
2024-04-02 17:16 ` [pve-devel] [PATCH pve-manager 37/37] firewall: expose " Stefan Hanreich
2024-04-02 20:47 ` [pve-devel] [RFC container/firewall/manager/proxmox-firewall/qemu-server 00/37] proxmox firewall nftables implementation Laurent GUERBY
2024-04-03  7:33   ` Stefan Hanreich
     [not found] ` <mailman.54.1712122640.450.pve-devel@lists.proxmox.com>
2024-04-03  7:52   ` Stefan Hanreich
2024-04-03 12:26   ` Stefan Hanreich
     [not found] ` <mailman.56.1712124362.450.pve-devel@lists.proxmox.com>
2024-04-03  8:15   ` Stefan Hanreich
     [not found]     ` <mailman.77.1712145853.450.pve-devel@lists.proxmox.com>
2024-04-03 12:25       ` Stefan Hanreich
     [not found]         ` <mailman.78.1712149473.450.pve-devel@lists.proxmox.com>
2024-04-03 13:08           ` Stefan Hanreich
2024-04-03 10:46 ` Max Carrara
2024-04-09  9:21   ` Stefan Hanreich
2024-04-10 10:25 ` Lukas Wagner
2024-04-11  5:21   ` Stefan Hanreich
2024-04-11  7:34     ` Thomas Lamprecht
2024-04-11  7:55       ` Stefan Hanreich

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240402171629.536804-26-s.hanreich@proxmox.com \
    --to=s.hanreich@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    --cc=w.bumiller@proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal