From 5b6cc35deb80b73db83ce077231d2ade034b6caf Mon Sep 17 00:00:00 2001 From: ppom Date: Wed, 25 Feb 2026 12:00:00 +0100 Subject: [PATCH] nftables: Fix compilation errors and actually use libnftables --- .../reaction-plugin-nftables/src/action.rs | 8 +- plugins/reaction-plugin-nftables/src/main.rs | 17 ++- plugins/reaction-plugin-nftables/src/nft.rs | 100 ++++++++++-------- 3 files changed, 72 insertions(+), 53 deletions(-) diff --git a/plugins/reaction-plugin-nftables/src/action.rs b/plugins/reaction-plugin-nftables/src/action.rs index 6649bd1..3898ab5 100644 --- a/plugins/reaction-plugin-nftables/src/action.rs +++ b/plugins/reaction-plugin-nftables/src/action.rs @@ -8,7 +8,6 @@ use std::{ use nftables::{ batch::Batch, expr::Expression, - helper::apply_ruleset_async, schema::{Element, NfListObject, Rule, SetFlag, SetType, SetTypeValue}, stmt::Statement, types::{NfFamily, NfHook}, @@ -247,13 +246,14 @@ impl Set { if let Some(set) = set { let family = NfFamily::INet; let table = Cow::from("reaction"); - let name = Cow::from(set.as_str()); // create set - batch.add(NfListObject::<'a>::Set(Box::new(nftables::schema::Set { + batch.add(NfListObject::<'a>::Set(Box::new(nftables::schema::Set::< + 'a, + > { family, table: table.to_owned(), - name, + name: Cow::Owned(set.to_owned()), // TODO Try a set which is both ipv4 and ipv6? set_type: SetTypeValue::Single(match version { Version::IPv4 => SetType::Ipv4Addr, diff --git a/plugins/reaction-plugin-nftables/src/main.rs b/plugins/reaction-plugin-nftables/src/main.rs index ac93eba..13e14a7 100644 --- a/plugins/reaction-plugin-nftables/src/main.rs +++ b/plugins/reaction-plugin-nftables/src/main.rs @@ -3,7 +3,6 @@ use std::{ collections::{BTreeMap, BTreeSet}, }; -use libnftables1_sys::Nftables; use nftables::{ batch::Batch, schema::{Chain, NfListObject, Table}, @@ -15,7 +14,10 @@ use reaction_plugin::{ }; use remoc::rtc; -use crate::action::{Action, ActionOptions, Set, SetOptions}; +use crate::{ + action::{Action, ActionOptions, Set, SetOptions}, + nft::NftClient, +}; #[cfg(test)] mod tests; @@ -32,6 +34,7 @@ async fn main() { #[derive(Default)] struct Plugin { + nft: NftClient, sets: Vec, actions: Vec, shutdown: ShutdownController, @@ -90,8 +93,12 @@ impl PluginInfo for Plugin { .map_err(|err| format!("set {}: {err}", options.set))?; let (tx, rx) = remoc::rch::mpsc::channel(1); - self.actions - .push(Action::new(self.shutdown.token(), rx, options)?); + self.actions.push(Action::new( + self.nft.clone(), + self.shutdown.token(), + rx, + options, + )?); ret_actions.push(ActionImpl { tx }); } @@ -133,7 +140,7 @@ impl PluginInfo for Plugin { } // TODO apply batch - Nftables::new(); + self.nft.send(batch).await?; // Launch a task that will destroy the table on shutdown { diff --git a/plugins/reaction-plugin-nftables/src/nft.rs b/plugins/reaction-plugin-nftables/src/nft.rs index 45c4f5c..a1641d2 100644 --- a/plugins/reaction-plugin-nftables/src/nft.rs +++ b/plugins/reaction-plugin-nftables/src/nft.rs @@ -7,26 +7,65 @@ use libnftables1_sys::Nftables; use nftables::batch::Batch; use tokio::sync::{mpsc, oneshot}; -pub fn nftables_thread() -> NftClient { - let (tx, mut rx) = mpsc::channel(10); +/// A client with a dedicated server thread to libnftables. +/// Calling [`Default::default()`] spawns a new server thread. +/// Cloning just creates a new client to the same server thread. +#[derive(Clone)] +pub struct NftClient { + tx: mpsc::Sender, +} - thread::spawn(move || { - let mut conn = Nftables::new(); +impl Default for NftClient { + fn default() -> Self { + let (tx, mut rx) = mpsc::channel(10); - while let Some(NftCommand { json, ret }) = rx.blocking_recv() { - let (rc, output, error) = conn.run_cmd(json.as_ptr()); - let res = match rc { - 0 => to_rust_string(output) - .ok_or_else(|| "unknown ok (rc = 0 but no output buffer)".into()), - _ => to_rust_string(error) - .map(|err| format!("error (rc = {rc}: {err})")) - .ok_or_else(|| format!("unknown error (rc = {rc} but no error buffer)")), - }; - ret.send(res); - } - }); + thread::spawn(move || { + let mut conn = Nftables::new(); - NftClient { tx } + while let Some(NftCommand { json, ret }) = rx.blocking_recv() { + let (rc, output, error) = conn.run_cmd(json.as_ptr()); + let res = match rc { + 0 => to_rust_string(output) + .ok_or_else(|| "unknown ok (rc = 0 but no output buffer)".into()), + _ => to_rust_string(error) + .map(|err| format!("error (rc = {rc}: {err})")) + .ok_or_else(|| format!("unknown error (rc = {rc} but no error buffer)")), + }; + let _ = ret.send(res); + } + }); + + NftClient { tx } + } +} + +impl NftClient { + /// Send a batch to nftables. + pub async fn send(&self, batch: Batch<'_>) -> Result { + // convert JSON to CString + let mut json = serde_json::to_vec(&batch.to_nftables()) + .map_err(|err| format!("couldn't build json to send to nftables: {err}"))?; + json.push('\0' as u8); + let json = CString::from_vec_with_nul(json) + .map_err(|err| format!("invalid json with null char: {err}"))?; + + // Send command + let (tx, rx) = oneshot::channel(); + let command = NftCommand { json, ret: tx }; + self.tx + .send(command) + .await + .map_err(|err| format!("nftables thread has quit, can't send command: {err}"))?; + + // Wait for result + rx.await + .map_err(|_| format!("nftables thread has quit, no response for command"))? + } +} + +struct NftCommand { + json: CString, + ret: oneshot::Sender>, } fn to_rust_string(c_ptr: *const i8) -> Option { @@ -40,30 +79,3 @@ fn to_rust_string(c_ptr: *const i8) -> Option { ) } } - -pub struct NftClient { - tx: mpsc::Sender, -} - -impl NftClient { - pub async fn send(&self, batch: Batch<'_>) -> Result { - // convert JSON to CString - let mut json = serde_json::to_vec(&batch.to_nftables()) - .map_err(|err| format!("couldn't build json to send to nftables: {err}"))?; - json.push('\0' as u8); - let json = CString::from_vec_with_nul(json) - .map_err(|err| format!("invalid json with null char: {err}"))?; - // Send command - let (tx, rx) = oneshot::channel(); - let command = NftCommand { json, ret: tx }; - self.tx.send(command).await; - // Wait for result - rx.await - .map_err(|err| format!("nftables thread has quit: {err}"))? - } -} - -struct NftCommand { - json: CString, - ret: oneshot::Sender>, -}