diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..4a4726a --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use_nix diff --git a/.gitignore b/.gitignore index a9717f0..f342516 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ import-rust-db/target reaction-plugin/target /local .ccls-cache +.direnv diff --git a/Cargo.lock b/Cargo.lock index 0c018d6..dac6c60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index b9c7a9e..3e72f6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ members = [ "crates/treedb", "plugins/reaction-plugin", "plugins/reaction-plugin-cluster", + "plugins/reaction-plugin-ipset", "plugins/reaction-plugin-virtual" ] diff --git a/plugins/reaction-plugin-ipset/Cargo.toml b/plugins/reaction-plugin-ipset/Cargo.toml new file mode 100644 index 0000000..e6247e2 --- /dev/null +++ b/plugins/reaction-plugin-ipset/Cargo.toml @@ -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" diff --git a/plugins/reaction-plugin-ipset/src/ipset.rs b/plugins/reaction-plugin-ipset/src/ipset.rs new file mode 100644 index 0000000..6f841fb --- /dev/null +++ b/plugins/reaction-plugin-ipset/src/ipset.rs @@ -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, +} + +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, String> { + let (tx, rx) = mpsc::channel(1); + thread::spawn(move || IPsetManager::default().serve(rx)); + Ok(tx) +} + +struct Set { + session: Session, + version: Version, +} + +#[derive(Default)] +struct IPsetManager { + sessions: BTreeMap, +} + +impl IPsetManager { + fn serve(&mut self, mut rx: mpsc::Receiver) { + 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 = 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(()) + } +} diff --git a/plugins/reaction-plugin-ipset/src/main.rs b/plugins/reaction-plugin-ipset/src/main.rs new file mode 100644 index 0000000..47b4e3d --- /dev/null +++ b/plugins/reaction-plugin-ipset/src/main.rs @@ -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>, + actions: Vec, +} + +impl PluginInfo for Plugin { + async fn manifest(&mut self) -> Result { + 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 { + 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, + ) -> RemoteResult { + 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(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match Option::::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(&self, serializer: S) -> Result + 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, + // 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 { + vec!["INPUT".into(), "FORWARD".into()] +} + +struct Action { + ipv4_set: Option, + ipv6_set: Option, + // index of pattern ip in match vec + ip_index: usize, + chains: Vec, + rx: mpsc::Receiver, +} + +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(); + // } + } +} diff --git a/plugins/reaction-plugin-ipset/src/tests.rs b/plugins/reaction-plugin-ipset/src/tests.rs new file mode 100644 index 0000000..b08f416 --- /dev/null +++ b/plugins/reaction-plugin-ipset/src/tests.rs @@ -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 ", "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 ", "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": " 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), + ); +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..e761b07 --- /dev/null +++ b/shell.nix @@ -0,0 +1,12 @@ +with import {}; +pkgs.mkShell { + name = "libipset"; + buildInputs = [ + ipset + clang + ]; + src = null; + shellHook = '' + export LIBCLANG_PATH="$(clang -print-file-name=libclang.so)" + ''; +} diff --git a/tests/test-conf/test-ipset.jsonnet b/tests/test-conf/test-ipset.jsonnet new file mode 100644 index 0000000..e749324 --- /dev/null +++ b/tests/test-conf/test-ipset.jsonnet @@ -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: ['^$'], + actions: { + a0: { + type: 'ipset', + options: { + set: 'reactiontest', + pattern: 'ip', + version: 46, + chains: ['INPUT', 'FORWARD'], + }, + }, + b0: { + type: 'cluster_send', + options: { + send: 'NODE b0 ', + 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: ['^$'], + actions: { + a1: { + cmd: ['sh', '-c', 'echo >>./log'], + }, + }, + }, + }, + }, + }, +}