nftables: Fix compilation errors and actually use libnftables

This commit is contained in:
ppom 2026-02-25 12:00:00 +01:00
commit 5b6cc35deb
No known key found for this signature in database
3 changed files with 61 additions and 42 deletions

View file

@ -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,

View file

@ -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<Set>,
actions: Vec<Action>,
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
{

View file

@ -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<NftCommand>,
}
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<String, String> {
// 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<Result<String, String>>,
}
fn to_rust_string(c_ptr: *const i8) -> Option<String> {
@ -40,30 +79,3 @@ fn to_rust_string(c_ptr: *const i8) -> Option<String> {
)
}
}
pub struct NftClient {
tx: mpsc::Sender<NftCommand>,
}
impl NftClient {
pub async fn send(&self, batch: Batch<'_>) -> Result<String, String> {
// 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<Result<String, String>>,
}