mirror of
https://framagit.org/ppom/reaction
synced 2026-03-14 12:45:47 +01:00
reaction-plugin-ipset: first work session
This commit is contained in:
parent
959c32c01e
commit
3a6260fa26
10 changed files with 838 additions and 8 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
use_nix
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -21,3 +21,4 @@ import-rust-db/target
|
|||
reaction-plugin/target
|
||||
/local
|
||||
.ccls-cache
|
||||
.direnv
|
||||
|
|
|
|||
169
Cargo.lock
generated
169
Cargo.lock
generated
|
|
@ -257,6 +257,26 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash 1.1.0",
|
||||
"shlex",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
|
|
@ -332,6 +352,15 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
|
|
@ -382,6 +411,17 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.54"
|
||||
|
|
@ -669,13 +709,34 @@ dependencies = [
|
|||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
|
||||
dependencies = [
|
||||
"derive_more-impl 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
|
||||
dependencies = [
|
||||
"derive_more-impl",
|
||||
"derive_more-impl 2.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more-impl"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -785,6 +846,12 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "embedded-io"
|
||||
version = "0.4.0"
|
||||
|
|
@ -1030,6 +1097,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.18"
|
||||
|
|
@ -1515,6 +1588,29 @@ version = "2.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "ipset"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3f6539e7df6df265a48ac3bfa1cc1b9fb37b604c92e9ab01521865e2323787f"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"derive_more 1.0.0",
|
||||
"ipset_derive",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipset_derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db6d64e879badf39e93df3831cb2902d290a2db61f8a46f299a681a2a601e0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iri-string"
|
||||
version = "0.7.10"
|
||||
|
|
@ -1537,7 +1633,7 @@ dependencies = [
|
|||
"cfg_aliases",
|
||||
"crypto_box",
|
||||
"data-encoding",
|
||||
"derive_more",
|
||||
"derive_more 2.1.1",
|
||||
"ed25519-dalek",
|
||||
"futures-util",
|
||||
"getrandom 0.3.4",
|
||||
|
|
@ -1588,7 +1684,7 @@ checksum = "25a8c5fb1cc65589f0d7ab44269a76f615a8c4458356952c9b0ef1c93ea45ff8"
|
|||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"data-encoding",
|
||||
"derive_more",
|
||||
"derive_more 2.1.1",
|
||||
"ed25519-dalek",
|
||||
"n0-error",
|
||||
"rand_core 0.9.5",
|
||||
|
|
@ -1689,7 +1785,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"cfg_aliases",
|
||||
"data-encoding",
|
||||
"derive_more",
|
||||
"derive_more 2.1.1",
|
||||
"getrandom 0.3.4",
|
||||
"hickory-resolver",
|
||||
"http",
|
||||
|
|
@ -1732,6 +1828,15 @@ version = "1.70.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
|
|
@ -1865,6 +1970,16 @@ version = "0.2.180"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
|
|
@ -1947,6 +2062,12 @@ version = "2.7.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.1"
|
||||
|
|
@ -2003,7 +2124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"derive_more",
|
||||
"derive_more 2.1.1",
|
||||
"futures-buffered",
|
||||
"futures-lite",
|
||||
"futures-util",
|
||||
|
|
@ -2023,7 +2144,7 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38acf13c1ddafc60eb7316d52213467f8ccb70b6f02b65e7d97f7799b1f50be4"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"derive_more 2.1.1",
|
||||
"n0-error",
|
||||
"n0-future",
|
||||
]
|
||||
|
|
@ -2102,7 +2223,7 @@ dependencies = [
|
|||
"atomic-waker",
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
"derive_more",
|
||||
"derive_more 2.1.1",
|
||||
"iroh-quinn-udp",
|
||||
"js-sys",
|
||||
"libc",
|
||||
|
|
@ -2139,6 +2260,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "normalize-line-endings"
|
||||
version = "0.3.0"
|
||||
|
|
@ -2428,7 +2559,7 @@ checksum = "7b575f975dcf03e258b0c7ab3f81497d7124f508884c37da66a7314aa2a8d467"
|
|||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"derive_more",
|
||||
"derive_more 2.1.1",
|
||||
"futures-lite",
|
||||
"futures-util",
|
||||
"hyper-util",
|
||||
|
|
@ -2538,6 +2669,16 @@ dependencies = [
|
|||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
|
|
@ -2746,6 +2887,18 @@ dependencies = [
|
|||
"treedb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reaction-plugin-ipset"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ipset",
|
||||
"reaction-plugin",
|
||||
"remoc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reaction-plugin-virtual"
|
||||
version = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ members = [
|
|||
"crates/treedb",
|
||||
"plugins/reaction-plugin",
|
||||
"plugins/reaction-plugin-cluster",
|
||||
"plugins/reaction-plugin-ipset",
|
||||
"plugins/reaction-plugin-virtual"
|
||||
]
|
||||
|
||||
|
|
|
|||
12
plugins/reaction-plugin-ipset/Cargo.toml
Normal file
12
plugins/reaction-plugin-ipset/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "reaction-plugin-ipset"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||
remoc.workspace = true
|
||||
reaction-plugin.path = "../reaction-plugin"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
ipset = "0.9.0"
|
||||
122
plugins/reaction-plugin-ipset/src/ipset.rs
Normal file
122
plugins/reaction-plugin-ipset/src/ipset.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
use std::{collections::BTreeMap, process::Command, thread};
|
||||
|
||||
use ipset::{
|
||||
Session,
|
||||
types::{Error, HashNet},
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum Version {
|
||||
IPv4,
|
||||
IPv6,
|
||||
}
|
||||
|
||||
pub struct SetOptions {
|
||||
name: String,
|
||||
version: Version,
|
||||
timeout: Option<u32>,
|
||||
}
|
||||
|
||||
pub struct SetChain {
|
||||
set: String,
|
||||
chain: String,
|
||||
action: String,
|
||||
}
|
||||
|
||||
pub enum Order {
|
||||
CreateSet(SetOptions),
|
||||
DestroySet(String),
|
||||
InsertSet(SetChain),
|
||||
RemoveSet(SetChain),
|
||||
}
|
||||
|
||||
pub fn ipset_thread() -> Result<mpsc::Sender<Order>, String> {
|
||||
let (tx, rx) = mpsc::channel(1);
|
||||
thread::spawn(move || IPsetManager::default().serve(rx));
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
struct Set {
|
||||
session: Session<HashNet>,
|
||||
version: Version,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct IPsetManager {
|
||||
sessions: BTreeMap<String, Set>,
|
||||
}
|
||||
|
||||
impl IPsetManager {
|
||||
fn serve(&mut self, mut rx: mpsc::Receiver<Order>) {
|
||||
loop {
|
||||
match rx.blocking_recv() {
|
||||
None => break,
|
||||
Some(order) => {
|
||||
let result = self.handle_order(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_order(&mut self, order: Order) -> Result<(), String> {
|
||||
match order {
|
||||
Order::CreateSet(SetOptions {
|
||||
name,
|
||||
version,
|
||||
timeout,
|
||||
}) => {
|
||||
let mut session: Session<HashNet> = Session::new(name.clone());
|
||||
session
|
||||
.create(|builder| {
|
||||
let builder = if let Some(timeout) = timeout {
|
||||
builder.with_timeout(timeout)?
|
||||
} else {
|
||||
builder
|
||||
};
|
||||
builder.with_ipv6(version == Version::IPv6)?.build()
|
||||
})
|
||||
.map_err(|err| format!("Could not create set {name}: {err}"))?;
|
||||
|
||||
self.sessions.insert(name, Set { session, version });
|
||||
}
|
||||
Order::DestroySet(set) => {
|
||||
if let Some(mut session) = self.sessions.remove(&set) {
|
||||
session
|
||||
.session
|
||||
.destroy()
|
||||
.map_err(|err| format!("Could not destroy set {set}: {err}"))?;
|
||||
}
|
||||
}
|
||||
Order::InsertSet(SetChain { set, chain, action }) => {
|
||||
let child = Command::new("iptables")
|
||||
.args([
|
||||
"-w",
|
||||
"-I",
|
||||
&chain,
|
||||
"-m",
|
||||
"set",
|
||||
"--match-set",
|
||||
&set,
|
||||
"src",
|
||||
"-j",
|
||||
&action,
|
||||
])
|
||||
.spawn()
|
||||
.map_err(|err| {
|
||||
format!("Could not insert ipset {set} in chain {chain}: {err}")
|
||||
})?;
|
||||
match child.wait() {
|
||||
Ok(exit) => {
|
||||
if !exit.success() {
|
||||
return Err(format!("Could not insert ipset"));
|
||||
}
|
||||
}
|
||||
Err(_) => todo!(),
|
||||
};
|
||||
}
|
||||
Order::RemoveSet(options) => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
187
plugins/reaction-plugin-ipset/src/main.rs
Normal file
187
plugins/reaction-plugin-ipset/src/main.rs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
use std::collections::BTreeSet;
|
||||
|
||||
use reaction_plugin::{
|
||||
ActionImpl, Exec, Hello, Manifest, PluginInfo, RemoteResult, StreamImpl, Value,
|
||||
};
|
||||
use remoc::{rch::mpsc, rtc};
|
||||
use serde::{Deserialize, Serialize, de::Deserializer, de::Error};
|
||||
|
||||
use crate::ipset::ipset_thread;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
mod ipset;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let plugin = Plugin::default();
|
||||
reaction_plugin::main_loop(plugin).await;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Plugin {
|
||||
// ipset: Arc<Mutex<IPSet>>,
|
||||
actions: Vec<Action>,
|
||||
}
|
||||
|
||||
impl PluginInfo for Plugin {
|
||||
async fn manifest(&mut self) -> Result<Manifest, rtc::CallError> {
|
||||
Ok(Manifest {
|
||||
hello: Hello::new(),
|
||||
streams: BTreeSet::default(),
|
||||
actions: BTreeSet::from(["ipset".into()]),
|
||||
})
|
||||
}
|
||||
|
||||
async fn stream_impl(
|
||||
&mut self,
|
||||
_stream_name: String,
|
||||
_stream_type: String,
|
||||
_config: Value,
|
||||
) -> RemoteResult<StreamImpl> {
|
||||
Err("This plugin can't handle any stream type".into())
|
||||
}
|
||||
|
||||
async fn action_impl(
|
||||
&mut self,
|
||||
stream_name: String,
|
||||
filter_name: String,
|
||||
action_name: String,
|
||||
action_type: String,
|
||||
config: Value,
|
||||
patterns: Vec<String>,
|
||||
) -> RemoteResult<ActionImpl> {
|
||||
if &action_type != "ipset" {
|
||||
return Err("This plugin can't handle other action types than ipset".into());
|
||||
}
|
||||
|
||||
let mut options: ActionOptions = serde_json::from_value(config.into()).map_err(|err| {
|
||||
format!("invalid options for action {stream_name}.{filter_name}.{action_name}: {err}")
|
||||
})?;
|
||||
|
||||
let ip_index = patterns
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|(_, name)| name == &options.pattern)
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"No pattern with name {} in filter {stream_name}.{filter_name}",
|
||||
options.pattern
|
||||
)
|
||||
})?
|
||||
.0;
|
||||
|
||||
let (tx, rx) = remoc::rch::mpsc::channel(1);
|
||||
self.actions.push(Action {
|
||||
chains: options.chains,
|
||||
ipv4_set: match options.version {
|
||||
IpVersion::V4 => Some(options.set.clone()),
|
||||
IpVersion::V6 => None,
|
||||
IpVersion::V46 => Some(format!("{}v4", options.set)),
|
||||
},
|
||||
ipv6_set: match options.version {
|
||||
IpVersion::V4 => None,
|
||||
IpVersion::V6 => Some(options.set),
|
||||
IpVersion::V46 => Some(format!("{}v6", options.set)),
|
||||
},
|
||||
ip_index,
|
||||
rx,
|
||||
});
|
||||
|
||||
Ok(ActionImpl { tx })
|
||||
}
|
||||
|
||||
async fn finish_setup(&mut self) -> RemoteResult<()> {
|
||||
ipset_thread()?;
|
||||
|
||||
todo!();
|
||||
}
|
||||
|
||||
async fn close(self) -> RemoteResult<()> {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
enum IpVersion {
|
||||
V4,
|
||||
V6,
|
||||
V46,
|
||||
}
|
||||
impl<'de> Deserialize<'de> for IpVersion {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
match Option::<u8>::deserialize(deserializer)? {
|
||||
None => Ok(IpVersion::V46),
|
||||
Some(version) => match version {
|
||||
4 => Ok(IpVersion::V4),
|
||||
6 => Ok(IpVersion::V6),
|
||||
46 => Ok(IpVersion::V46),
|
||||
_ => Err(D::Error::custom("version must be one of 4, 6 or 46")),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Serialize for IpVersion {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_u8(match self {
|
||||
IpVersion::V4 => 4,
|
||||
IpVersion::V6 => 6,
|
||||
IpVersion::V46 => 46,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct ActionOptions {
|
||||
/// The set that should be used by this action
|
||||
set: String,
|
||||
/// The pattern name of the IP.
|
||||
/// Defaults to "ip"
|
||||
#[serde(default = "serde_ip")]
|
||||
pattern: String,
|
||||
/// The IP type.
|
||||
/// Defaults to `46`.
|
||||
/// If `4`: creates an IPv4 set with this name
|
||||
/// If `6`: creates an IPv6 set with this name
|
||||
/// If `46`: creates an IPv4 set with its name suffixed by 'v4' AND an IPv6 set with its name suffixed by 'v6'
|
||||
version: IpVersion,
|
||||
/// Chains where the IP set should be inserted.
|
||||
/// Defaults to `["INPUT", "FORWARD"]`
|
||||
#[serde(default = "serde_chains")]
|
||||
chains: Vec<String>,
|
||||
// Optional timeout, letting linux/netfilter handle set removal instead of reaction
|
||||
// Note that `reaction show` and `reaction flush` won't work if set instead of an `after` action
|
||||
}
|
||||
|
||||
fn serde_ip() -> String {
|
||||
"ip".into()
|
||||
}
|
||||
fn serde_chains() -> Vec<String> {
|
||||
vec!["INPUT".into(), "FORWARD".into()]
|
||||
}
|
||||
|
||||
struct Action {
|
||||
ipv4_set: Option<String>,
|
||||
ipv6_set: Option<String>,
|
||||
// index of pattern ip in match vec
|
||||
ip_index: usize,
|
||||
chains: Vec<String>,
|
||||
rx: mpsc::Receiver<Exec>,
|
||||
}
|
||||
|
||||
impl Action {
|
||||
async fn serve(&mut self) {
|
||||
// while let Ok(Some(exec)) = self.rx.recv().await {
|
||||
// let line = self.send.line(exec.match_);
|
||||
// self.to.tx.send((line, exec.time)).await.unwrap();
|
||||
// }
|
||||
}
|
||||
}
|
||||
268
plugins/reaction-plugin-ipset/src/tests.rs
Normal file
268
plugins/reaction-plugin-ipset/src/tests.rs
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use reaction_plugin::{Exec, PluginInfo, Value};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::Plugin;
|
||||
|
||||
#[tokio::test]
|
||||
async fn conf_stream() {
|
||||
// Invalid type
|
||||
assert!(
|
||||
Plugin::default()
|
||||
.stream_impl("stream".into(), "virtu".into(), Value::Null)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
|
||||
assert!(
|
||||
Plugin::default()
|
||||
.stream_impl("stream".into(), "virtual".into(), Value::Null)
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
eprintln!(
|
||||
"err: {:?}",
|
||||
Plugin::default()
|
||||
.stream_impl("stream".into(), "virtual".into(), json!({}).into())
|
||||
.await
|
||||
);
|
||||
assert!(
|
||||
Plugin::default()
|
||||
.stream_impl("stream".into(), "virtual".into(), json!({}).into())
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
// Invalid conf: must be empty
|
||||
assert!(
|
||||
Plugin::default()
|
||||
.stream_impl(
|
||||
"stream".into(),
|
||||
"virtual".into(),
|
||||
json!({"key": "value" }).into()
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn conf_action() {
|
||||
let valid_conf = json!({ "send": "message", "to": "stream" });
|
||||
|
||||
let missing_send_conf = json!({ "to": "stream" });
|
||||
let missing_to_conf = json!({ "send": "stream" });
|
||||
let extra_attr_conf = json!({ "send": "message", "send2": "message", "to": "stream" });
|
||||
|
||||
let patterns = Vec::default();
|
||||
|
||||
// Invalid type
|
||||
assert!(
|
||||
Plugin::default()
|
||||
.action_impl(
|
||||
"stream".into(),
|
||||
"filter".into(),
|
||||
"action".into(),
|
||||
"virtu".into(),
|
||||
Value::Null,
|
||||
patterns.clone()
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
Plugin::default()
|
||||
.action_impl(
|
||||
"stream".into(),
|
||||
"filter".into(),
|
||||
"action".into(),
|
||||
"virtual".into(),
|
||||
valid_conf.into(),
|
||||
patterns.clone()
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
for conf in [missing_send_conf, missing_to_conf, extra_attr_conf] {
|
||||
assert!(
|
||||
Plugin::default()
|
||||
.action_impl(
|
||||
"stream".into(),
|
||||
"filter".into(),
|
||||
"action".into(),
|
||||
"virtual".into(),
|
||||
conf.clone().into(),
|
||||
patterns.clone()
|
||||
)
|
||||
.await
|
||||
.is_err(),
|
||||
"conf: {:?}",
|
||||
conf
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn conf_send() {
|
||||
// Valid to: option
|
||||
let mut plugin = Plugin::default();
|
||||
plugin
|
||||
.stream_impl("stream".into(), "virtual".into(), Value::Null)
|
||||
.await
|
||||
.unwrap();
|
||||
plugin
|
||||
.action_impl(
|
||||
"stream".into(),
|
||||
"filter".into(),
|
||||
"action".into(),
|
||||
"virtual".into(),
|
||||
json!({ "send": "message", "to": "stream" }).into(),
|
||||
Vec::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(plugin.finish_setup().await.is_ok());
|
||||
|
||||
// Invalid to: option
|
||||
let mut plugin = Plugin::default();
|
||||
plugin
|
||||
.stream_impl("stream".into(), "virtual".into(), Value::Null)
|
||||
.await
|
||||
.unwrap();
|
||||
plugin
|
||||
.action_impl(
|
||||
"stream".into(),
|
||||
"filter".into(),
|
||||
"action".into(),
|
||||
"virtual".into(),
|
||||
json!({ "send": "message", "to": "stream1" }).into(),
|
||||
Vec::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(plugin.finish_setup().await.is_err());
|
||||
}
|
||||
|
||||
// Let's allow empty streams for now.
|
||||
// I guess it can be useful to have manual only actions.
|
||||
//
|
||||
// #[tokio::test]
|
||||
// async fn conf_empty_stream() {
|
||||
// let mut plugin = Plugin::default();
|
||||
// plugin
|
||||
// .stream_impl("stream".into(), "virtual".into(), Value::Null)
|
||||
// .await
|
||||
// .unwrap();
|
||||
// assert!(plugin.finish_setup().await.is_err());
|
||||
// }
|
||||
|
||||
#[tokio::test]
|
||||
async fn run_simple() {
|
||||
let mut plugin = Plugin::default();
|
||||
let mut stream = plugin
|
||||
.stream_impl("stream".into(), "virtual".into(), Value::Null)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!stream.standalone);
|
||||
|
||||
let action = plugin
|
||||
.action_impl(
|
||||
"stream".into(),
|
||||
"filter".into(),
|
||||
"action".into(),
|
||||
"virtual".into(),
|
||||
json!({ "send": "message <test>", "to": "stream" }).into(),
|
||||
vec!["test".into()],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(plugin.finish_setup().await.is_ok());
|
||||
|
||||
for m in ["test1", "test2", "test3", " a a a aa a a"] {
|
||||
let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
assert!(
|
||||
action
|
||||
.tx
|
||||
.send(Exec {
|
||||
match_: vec![m.into()],
|
||||
time,
|
||||
})
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
assert_eq!(
|
||||
stream.stream.recv().await.unwrap().unwrap(),
|
||||
(format!("message {m}"), time),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn run_two_actions() {
|
||||
let mut plugin = Plugin::default();
|
||||
let mut stream = plugin
|
||||
.stream_impl("stream".into(), "virtual".into(), Value::Null)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!stream.standalone);
|
||||
|
||||
let action1 = plugin
|
||||
.action_impl(
|
||||
"stream".into(),
|
||||
"filter".into(),
|
||||
"action".into(),
|
||||
"virtual".into(),
|
||||
json!({ "send": "send <a>", "to": "stream" }).into(),
|
||||
vec!["a".into(), "b".into()],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let action2 = plugin
|
||||
.action_impl(
|
||||
"stream".into(),
|
||||
"filter".into(),
|
||||
"action".into(),
|
||||
"virtual".into(),
|
||||
json!({ "send": "<b> send", "to": "stream" }).into(),
|
||||
vec!["a".into(), "b".into()],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(plugin.finish_setup().await.is_ok());
|
||||
|
||||
let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
|
||||
assert!(
|
||||
action1
|
||||
.tx
|
||||
.send(Exec {
|
||||
match_: vec!["aa".into(), "bb".into()],
|
||||
time,
|
||||
})
|
||||
.await
|
||||
.is_ok(),
|
||||
);
|
||||
assert_eq!(
|
||||
stream.stream.recv().await.unwrap().unwrap(),
|
||||
("send aa".into(), time),
|
||||
);
|
||||
|
||||
assert!(
|
||||
action2
|
||||
.tx
|
||||
.send(Exec {
|
||||
match_: vec!["aa".into(), "bb".into()],
|
||||
time,
|
||||
})
|
||||
.await
|
||||
.is_ok(),
|
||||
);
|
||||
assert_eq!(
|
||||
stream.stream.recv().await.unwrap().unwrap(),
|
||||
("bb send".into(), time),
|
||||
);
|
||||
}
|
||||
12
shell.nix
Normal file
12
shell.nix
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
with import <nixpkgs> {};
|
||||
pkgs.mkShell {
|
||||
name = "libipset";
|
||||
buildInputs = [
|
||||
ipset
|
||||
clang
|
||||
];
|
||||
src = null;
|
||||
shellHook = ''
|
||||
export LIBCLANG_PATH="$(clang -print-file-name=libclang.so)"
|
||||
'';
|
||||
}
|
||||
73
tests/test-conf/test-ipset.jsonnet
Normal file
73
tests/test-conf/test-ipset.jsonnet
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
patterns: {
|
||||
ip: {
|
||||
type: 'ip',
|
||||
},
|
||||
all: {
|
||||
regex: @".*",
|
||||
},
|
||||
},
|
||||
|
||||
plugins: {
|
||||
cluster: {
|
||||
path: './target/debug/reaction-plugin-ipset',
|
||||
check_root: false,
|
||||
systemd_options: {
|
||||
DynamicUser: ['false'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
streams: {
|
||||
s0: {
|
||||
cmd: ['bash', '-c', 'sleep 1; for i in $(seq 4); do echo 192.0.2.$i; sleep 0.1; done'],
|
||||
filters: {
|
||||
f0: {
|
||||
regex: ['^<ip>$'],
|
||||
actions: {
|
||||
a0: {
|
||||
type: 'ipset',
|
||||
options: {
|
||||
set: 'reactiontest',
|
||||
pattern: 'ip',
|
||||
version: 46,
|
||||
chains: ['INPUT', 'FORWARD'],
|
||||
},
|
||||
},
|
||||
b0: {
|
||||
type: 'cluster_send',
|
||||
options: {
|
||||
send: 'NODE b0 <num>',
|
||||
to: 's1',
|
||||
},
|
||||
after: '1s',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
s1: {
|
||||
type: 'cluster',
|
||||
options: {
|
||||
listen_port: 1234,
|
||||
bind_ipv4: '127.0.0.1',
|
||||
bind_ipv6: null,
|
||||
message_timeout: '30s',
|
||||
nodes: [{
|
||||
public_key: 'PUBLIC_KEY',
|
||||
addresses: ['127.0.0.1:4321'],
|
||||
}],
|
||||
},
|
||||
filters: {
|
||||
f1: {
|
||||
regex: ['^<all>$'],
|
||||
actions: {
|
||||
a1: {
|
||||
cmd: ['sh', '-c', 'echo <all> >>./log'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue