[pve-devel] [PATCH proxmox-firewall v2 25/39] nftables: add libnftables bindings
Stefan Hanreich
s.hanreich at proxmox.com
Wed Apr 17 15:53:50 CEST 2024
Add a thin wrapper around libnftables, which can be used to run
commands defined by the rust types.
Reviewed-by: Lukas Wagner <l.wagner at proxmox.com>
Reviewed-by: Max Carrara <m.carrara at proxmox.com>
Co-authored-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
Signed-off-by: Stefan Hanreich <s.hanreich at 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
More information about the pve-devel
mailing list