From: Stefan Hanreich <s.hanreich@proxmox.com>
To: pve-devel@lists.proxmox.com
Cc: Wolfgang Bumiller <w.bumiller@proxmox.com>
Subject: [pve-devel] [PATCH proxmox-firewall v2 25/39] nftables: add libnftables bindings
Date: Wed, 17 Apr 2024 15:53:50 +0200 [thread overview]
Message-ID: <20240417135404.573490-26-s.hanreich@proxmox.com> (raw)
In-Reply-To: <20240417135404.573490-1-s.hanreich@proxmox.com>
Add a thin wrapper around libnftables, which can be used to run
commands defined by the rust types.
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
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
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2024-04-17 13:55 UTC|newest]
Thread overview: 41+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-04-17 13:53 [pve-devel] [PATCH container/docs/firewall/manager/proxmox-firewall/qemu-server v2 00/39] proxmox firewall nftables implementation Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 01/39] config: add proxmox-ve-config crate Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 02/39] config: firewall: add types for ip addresses Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 03/39] config: firewall: add types for ports Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 04/39] config: firewall: add types for log level and rate limit Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 05/39] config: firewall: add types for aliases Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 06/39] config: host: add helpers for host network configuration Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 07/39] config: guest: add helpers for parsing guest network config Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 08/39] config: firewall: add types for ipsets Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 09/39] config: firewall: add types for rules Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 10/39] config: firewall: add types for security groups Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 11/39] config: firewall: add generic parser for firewall configs Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 12/39] config: firewall: add cluster-specific config + option types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 13/39] config: firewall: add host specific " Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 14/39] config: firewall: add guest-specific " Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 15/39] config: firewall: add firewall macros Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 16/39] config: firewall: add conntrack helper types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 17/39] nftables: add crate for libnftables bindings Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 18/39] nftables: add helpers Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 19/39] nftables: expression: add types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 20/39] nftables: expression: implement conversion traits for firewall config Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 21/39] nftables: statement: add types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 22/39] nftables: statement: add conversion traits for config types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 23/39] nftables: commands: add types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 24/39] nftables: types: add conversion traits Stefan Hanreich
2024-04-17 13:53 ` Stefan Hanreich [this message]
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 26/39] firewall: add firewall crate Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 27/39] firewall: add base ruleset Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 28/39] firewall: add config loader Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 29/39] firewall: add rule generation logic Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 30/39] firewall: add object " Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 31/39] firewall: add ruleset " Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 32/39] firewall: add proxmox-firewall binary Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 33/39] firewall: add files for debian packaging Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 34/39] firewall: add integration test Stefan Hanreich
2024-04-17 13:54 ` [pve-devel] [PATCH qemu-server v2 35/39] firewall: add handling for new nft firewall Stefan Hanreich
2024-04-17 13:54 ` [pve-devel] [PATCH pve-container v2 36/39] " Stefan Hanreich
2024-04-17 13:54 ` [pve-devel] [PATCH pve-firewall v2 37/39] add configuration option for new nftables firewall Stefan Hanreich
2024-04-18 21:06 ` Thomas Lamprecht
2024-04-17 13:54 ` [pve-devel] [PATCH pve-manager v2 38/39] firewall: expose " Stefan Hanreich
2024-04-17 13:54 ` [pve-devel] [PATCH pve-docs v2 39/39] firewall: add documentation for proxmox-firewall 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=20240417135404.573490-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