mod bindings;
use std::os::raw::{c_char, c_uint};
pub use crate::bindings::*;
pub struct Nftables {
ctx: *mut nft_ctx,
}
impl Default for Nftables {
fn default() -> Self {
Self::new()
}
}
impl Nftables {
pub fn new() -> Nftables {
let ctx = unsafe { nft_ctx_new(0) };
unsafe { nft_ctx_buffer_output(ctx) };
unsafe { nft_ctx_buffer_error(ctx) };
Nftables { ctx }
}
#[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn run_cmd(&mut self, cmd: *const c_char) -> (i32, *const c_char, *const c_char) {
assert_ne!(self.ctx, ::std::ptr::null_mut());
if cmd.is_null() {
return (-1, ::std::ptr::null(), ::std::ptr::null());
}
let rc = unsafe { nft_run_cmd_from_buffer(self.ctx, cmd) };
let output = unsafe { nft_ctx_get_output_buffer(self.ctx) };
let error = unsafe { nft_ctx_get_error_buffer(self.ctx) };
(rc, output, error)
}
pub fn set_debug(&mut self, flags: nft_debug_level) {
unsafe { nft_ctx_output_set_debug(self.ctx, flags) };
}
pub fn get_debug(&self) -> nft_debug_level {
unsafe { nft_ctx_output_get_debug(self.ctx) }
}
fn set_flags(&mut self, flags: c_uint) {
let old_flags = unsafe { nft_ctx_output_get_flags(self.ctx) };
unsafe { nft_ctx_output_set_flags(self.ctx, old_flags | flags) };
}
pub fn set_output_handle(&mut self) {
self.set_flags(NFT_CTX_OUTPUT_HANDLE);
}
#[deprecated(
since = "1.0.0",
note = "please use `set_output_numeric_time()` instead"
)]
pub fn set_numeric_time(&mut self) {
self.set_flags(NFT_CTX_OUTPUT_NUMERIC_TIME);
}
pub fn set_output_numeric_time(&mut self) {
self.set_flags(NFT_CTX_OUTPUT_NUMERIC_TIME);
}
pub fn set_output_json(&mut self) {
self.set_flags(NFT_CTX_OUTPUT_JSON);
}
}
impl Drop for Nftables {
fn drop(&mut self) {
unsafe { nft_ctx_free(self.ctx) };
self.ctx = ::std::ptr::null_mut();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
extern "C" {
fn getuid() -> u32;
}
#[test]
fn list_ruleset() {
assert_eq!(unsafe { getuid() }, 0);
let mut nft = Nftables::new();
assert!(nft.ctx != ::std::ptr::null_mut());
let cmd = CStr::from_bytes_with_nul(b"list ruleset\0").unwrap();
let (rc, output, error) = nft.run_cmd(cmd.as_ptr());
assert_eq!(rc, 0);
assert_ne!(output, ::std::ptr::null());
assert_ne!(error, ::std::ptr::null());
}
#[test]
fn set_debug() {
let mut nft = Nftables::new();
assert!(nft.ctx != ::std::ptr::null_mut());
let lvl = nft.get_debug();
assert_eq!(lvl, 0);
nft.set_debug(NFT_DEBUG_SCANNER | NFT_DEBUG_EVALUATION | NFT_DEBUG_NETLINK);
let lvl = nft.get_debug();
assert_eq!(lvl, 0xd);
}
#[test]
fn set_output_flags() {
let mut nft = Nftables::new();
assert!(nft.ctx != ::std::ptr::null_mut());
let initial_flags = unsafe { nft_ctx_output_get_flags(nft.ctx) };
assert_eq!(initial_flags, 0);
nft.set_output_json();
let flags = unsafe { nft_ctx_output_get_flags(nft.ctx) };
assert_eq!(flags & NFT_CTX_OUTPUT_JSON, NFT_CTX_OUTPUT_JSON);
nft.set_output_handle();
let flags = unsafe { nft_ctx_output_get_flags(nft.ctx) };
assert_eq!(flags & NFT_CTX_OUTPUT_JSON, NFT_CTX_OUTPUT_JSON);
assert_eq!(flags & NFT_CTX_OUTPUT_HANDLE, NFT_CTX_OUTPUT_HANDLE);
nft.set_numeric_time();
let flags = unsafe { nft_ctx_output_get_flags(nft.ctx) };
assert_eq!(flags & NFT_CTX_OUTPUT_JSON, NFT_CTX_OUTPUT_JSON);
assert_eq!(flags & NFT_CTX_OUTPUT_HANDLE, NFT_CTX_OUTPUT_HANDLE);
assert_eq!(
flags & NFT_CTX_OUTPUT_NUMERIC_TIME,
NFT_CTX_OUTPUT_NUMERIC_TIME
);
}
#[test]
fn set_output_json_output() {
assert_eq!(unsafe { getuid() }, 0);
let mut nft = Nftables::new();
assert!(nft.ctx != ::std::ptr::null_mut());
nft.set_output_json();
let cmd = CStr::from_bytes_with_nul(b"list ruleset\0").unwrap();
let (rc, output, _error) = nft.run_cmd(cmd.as_ptr());
assert_eq!(rc, 0);
assert_ne!(output, ::std::ptr::null());
let output_str = unsafe { std::ffi::CStr::from_ptr(output) }
.to_str()
.unwrap();
if !output_str.is_empty() {
assert!(
output_str.trim_start().starts_with('{'),
"Expected JSON output to start with '{{', got: {}",
&output_str[..output_str.len().min(100)]
);
}
}
}