dnat + counters and dnat actions
parent
a844b32e27
commit
e45cb3fc89
|
@ -0,0 +1,112 @@
|
|||
use super::CounterAction;
|
||||
use crate::domain::{Action, Counters, CountersPort, ModuleArgs, Record, Singleton, Value};
|
||||
use crate::singleton_borrow;
|
||||
use chrono::Utc;
|
||||
|
||||
pub struct CounterRaise<C: CountersPort> {
|
||||
act: CounterAction<C>,
|
||||
}
|
||||
|
||||
impl<C: CountersPort> CounterRaise<C> {
|
||||
pub fn from_args<X: CountersPort>(
|
||||
args: ModuleArgs,
|
||||
counters: Singleton<Counters<X>>,
|
||||
) -> CounterRaise<X> {
|
||||
CounterRaise {
|
||||
act: CounterAction::<X>::from_args(args, counters, "CounterRaise", "keepSeconds"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CountersPort> Action for CounterRaise<C> {
|
||||
fn act(&mut self, record: &mut Record) -> Result<(), ()> {
|
||||
match record.get(&self.act.counter_key) {
|
||||
None => Err(()),
|
||||
Some(v) => {
|
||||
let count = singleton_borrow!(self.act.counters).augment(
|
||||
(self.act.counter_name.as_ref(), v),
|
||||
(1, self.act.duration.map(|d| Utc::now() + d)),
|
||||
);
|
||||
if let Some(s) = &self.act.save_into {
|
||||
record.insert(s.clone(), Value::Int(count as isize));
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::action::CounterRaise;
|
||||
use crate::domain::test_util::FakeCountersAdapter;
|
||||
use crate::domain::{Action, CounterData, Counters, Singleton, Value};
|
||||
use crate::{singleton_borrow, singleton_new, singleton_share};
|
||||
use chrono::{Duration, Utc};
|
||||
use std::collections::HashMap;
|
||||
use std::{thread, time};
|
||||
|
||||
#[test]
|
||||
fn when_non_existing_then_raise_to_1() {
|
||||
let (_, mut action) = get_counters_action();
|
||||
let mut record = HashMap::with_capacity(1);
|
||||
record.insert("k".to_string(), Value::Str("raise#1".to_string()));
|
||||
|
||||
action.act(&mut record).unwrap();
|
||||
assert_eq!(Some(&Value::Int(1)), record.get("raise"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_different_key_then_different_counter() {
|
||||
let (_, mut action) = get_counters_action();
|
||||
let mut record1 = HashMap::with_capacity(1);
|
||||
record1.insert("k".to_string(), Value::Str("raise#3".to_string()));
|
||||
let mut record2 = HashMap::with_capacity(1);
|
||||
record2.insert("k".to_string(), Value::Str("raise#4".to_string()));
|
||||
|
||||
action.act(&mut record1).unwrap();
|
||||
assert_eq!(Some(&Value::Int(1)), record1.get("raise"));
|
||||
action.act(&mut record2).unwrap();
|
||||
assert_eq!(Some(&Value::Int(1)), record2.get("raise"));
|
||||
action.act(&mut record2).unwrap();
|
||||
assert_eq!(Some(&Value::Int(2)), record2.get("raise"));
|
||||
action.act(&mut record2).unwrap();
|
||||
assert_eq!(Some(&Value::Int(3)), record2.get("raise"));
|
||||
action.act(&mut record1).unwrap();
|
||||
assert_eq!(Some(&Value::Int(2)), record1.get("raise"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_grace_time_then_count_is_0() {
|
||||
let (counters, mut action) = get_counters_action();
|
||||
let mut record = HashMap::with_capacity(1);
|
||||
record.insert("k".to_string(), Value::Str("raise#5".to_string()));
|
||||
singleton_borrow!(counters).insert(
|
||||
("test".to_string(), Value::Str("raise#5".to_string())),
|
||||
(0, Some(Utc::now() + Duration::seconds(1))),
|
||||
);
|
||||
|
||||
action.act(&mut record).unwrap();
|
||||
assert_eq!(Some(&Value::Int(0)), record.get("raise"));
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
action.act(&mut record).unwrap();
|
||||
assert_eq!(Some(&Value::Int(1)), record.get("raise"));
|
||||
}
|
||||
|
||||
fn get_counters_action() -> (
|
||||
Singleton<HashMap<(String, Value), CounterData>>,
|
||||
CounterRaise<FakeCountersAdapter>,
|
||||
) {
|
||||
let counters = singleton_new!(HashMap::new());
|
||||
let counters_backend =
|
||||
singleton_new!(Counters::<FakeCountersAdapter>::new(FakeCountersAdapter {
|
||||
counters: singleton_share!(counters)
|
||||
}));
|
||||
let mut args = HashMap::with_capacity(3);
|
||||
args.insert("counter".to_string(), Value::Str("test".to_string()));
|
||||
args.insert("for".to_string(), Value::Str("k".to_string()));
|
||||
args.insert("save".to_string(), Value::Str("raise".to_string()));
|
||||
let action = CounterRaise::<FakeCountersAdapter>::from_args(args, counters_backend);
|
||||
(counters, action)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
use super::CounterAction;
|
||||
use crate::domain::{Action, Counters, CountersPort, ModuleArgs, Record, Singleton, Value};
|
||||
use crate::singleton_borrow;
|
||||
use chrono::Utc;
|
||||
|
||||
pub struct CounterReset<C: CountersPort> {
|
||||
act: CounterAction<C>,
|
||||
}
|
||||
|
||||
impl<C: CountersPort> CounterReset<C> {
|
||||
pub fn from_args<X: CountersPort>(
|
||||
args: ModuleArgs,
|
||||
counters: Singleton<Counters<X>>,
|
||||
) -> CounterReset<X> {
|
||||
CounterReset {
|
||||
act: CounterAction::<X>::from_args(args, counters, "CounterReset", "graceSeconds"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CountersPort> Action for CounterReset<C> {
|
||||
fn act(&mut self, record: &mut Record) -> Result<(), ()> {
|
||||
match record.get(&self.act.counter_key) {
|
||||
None => Err(()),
|
||||
Some(v) => {
|
||||
let count = singleton_borrow!(self.act.counters).reset(
|
||||
(self.act.counter_name.as_ref(), v),
|
||||
self.act.duration.map(|d| Utc::now() + d),
|
||||
);
|
||||
if let Some(s) = &self.act.save_into {
|
||||
record.insert(s.clone(), Value::Int(count as isize));
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::action::CounterReset;
|
||||
use crate::domain::test_util::FakeCountersAdapter;
|
||||
use crate::domain::{Action, CounterData, Counters, Singleton, Value};
|
||||
use crate::{singleton_borrow, singleton_new, singleton_share};
|
||||
use chrono::{Duration, Utc};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn when_reset_without_gracetime_then_count_is_0_and_counter_removed() {
|
||||
let (counters, mut action) = get_counters_action(None);
|
||||
let mut record = HashMap::with_capacity(1);
|
||||
record.insert("k".to_string(), Value::Str("reset#1".to_string()));
|
||||
singleton_borrow!(counters).insert(
|
||||
("test".to_string(), Value::Str("reset#1".to_string())),
|
||||
(5, None),
|
||||
);
|
||||
|
||||
action.act(&mut record).unwrap();
|
||||
assert_eq!(Some(&Value::Int(0)), record.get("reset"));
|
||||
assert_eq!(0, singleton_borrow!(counters).len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_reset_with_gracetime_then_count_is_0_and_gracetime_is_stored() {
|
||||
let (counters, mut action) = get_counters_action(Some(5));
|
||||
let mut record = HashMap::with_capacity(1);
|
||||
record.insert("k".to_string(), Value::Str("reset#2".to_string()));
|
||||
|
||||
let almost = Utc::now() + Duration::seconds(5);
|
||||
let after = almost + Duration::seconds(1);
|
||||
action.act(&mut record).unwrap();
|
||||
assert_eq!(Some(&Value::Int(0)), record.get("reset"));
|
||||
let (c, od) = *(singleton_borrow!(counters)
|
||||
.get(&("test".to_string(), Value::Str("reset#2".to_string())))
|
||||
.unwrap());
|
||||
let d = od.unwrap();
|
||||
assert!(d >= almost);
|
||||
assert!(d < after);
|
||||
assert_eq!(0 as usize, c);
|
||||
}
|
||||
|
||||
fn get_counters_action(
|
||||
grace_time: Option<isize>,
|
||||
) -> (
|
||||
Singleton<HashMap<(String, Value), CounterData>>,
|
||||
CounterReset<FakeCountersAdapter>,
|
||||
) {
|
||||
let counters = singleton_new!(HashMap::new());
|
||||
let counters_backend =
|
||||
singleton_new!(Counters::<FakeCountersAdapter>::new(FakeCountersAdapter {
|
||||
counters: singleton_share!(counters)
|
||||
}));
|
||||
let mut args = HashMap::with_capacity(grace_time.map(|_| 4).unwrap_or(3));
|
||||
args.insert("counter".to_string(), Value::Str("test".to_string()));
|
||||
args.insert("for".to_string(), Value::Str("k".to_string()));
|
||||
args.insert("save".to_string(), Value::Str("reset".to_string()));
|
||||
if let Some(sec) = grace_time {
|
||||
args.insert("graceSeconds".to_string(), Value::Int(sec));
|
||||
}
|
||||
let action = CounterReset::<FakeCountersAdapter>::from_args(args, counters_backend);
|
||||
(counters, action)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
use super::{get_acceptable_key, remove_acceptable_key};
|
||||
use crate::domain::{Action, DnatMapping, DnatMappingsPort, ModuleArgs, Record, Singleton, Value};
|
||||
use crate::singleton_borrow;
|
||||
use chrono::{Duration, Utc};
|
||||
|
||||
type FieldAndValue = (Option<String>, Option<String>);
|
||||
|
||||
struct DnatMappingSpec {
|
||||
pub src_addr: FieldAndValue,
|
||||
pub src_port: FieldAndValue,
|
||||
pub internal_addr: FieldAndValue,
|
||||
pub internal_port: FieldAndValue,
|
||||
pub dest_addr: FieldAndValue,
|
||||
pub dest_port: FieldAndValue,
|
||||
pub keep_duration: Duration,
|
||||
}
|
||||
|
||||
pub struct DnatCapture {
|
||||
mappings: Singleton<dyn DnatMappingsPort>,
|
||||
specs: DnatMappingSpec,
|
||||
}
|
||||
|
||||
impl DnatCapture {
|
||||
pub fn from_args(mut args: ModuleArgs, mappings: Singleton<dyn DnatMappingsPort>) -> DnatCapture {
|
||||
let src_addr = (
|
||||
Some(
|
||||
remove_acceptable_key(&mut args, "saddr")
|
||||
.expect("The DnatCapture action needs a log field for the source address in “saddr”"),
|
||||
),
|
||||
None,
|
||||
);
|
||||
let src_port = (remove_acceptable_key(&mut args, "sport"), None);
|
||||
let internal_addr = (
|
||||
remove_acceptable_key(&mut args, "addr"),
|
||||
remove_acceptable_key(&mut args, "addrValue"),
|
||||
);
|
||||
if let &(None, None) = &internal_addr {
|
||||
panic!("The DnatCapture action requires either a field (“addr”) or a value (“addrValue”) for the internal address");
|
||||
}
|
||||
let internal_port = (
|
||||
remove_acceptable_key(&mut args, "port"),
|
||||
remove_acceptable_key(&mut args, "portValue"),
|
||||
);
|
||||
let dest_addr = (
|
||||
remove_acceptable_key(&mut args, "daddr"),
|
||||
remove_acceptable_key(&mut args, "daddrValue"),
|
||||
);
|
||||
let dest_port = (
|
||||
remove_acceptable_key(&mut args, "dport"),
|
||||
remove_acceptable_key(&mut args, "dportValue"),
|
||||
);
|
||||
let keep_duration = match args.remove("keepSeconds") {
|
||||
Some(Value::Int(i)) => Duration::seconds(i as i64),
|
||||
_ => Duration::seconds(63),
|
||||
};
|
||||
DnatCapture {
|
||||
mappings,
|
||||
specs: DnatMappingSpec {
|
||||
src_addr,
|
||||
src_port,
|
||||
internal_addr,
|
||||
internal_port,
|
||||
dest_addr,
|
||||
dest_port,
|
||||
keep_duration,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Action for DnatCapture {
|
||||
fn act(&mut self, record: &mut Record) -> Result<(), ()> {
|
||||
let src_addr = value_for(&self.specs.src_addr, record);
|
||||
let internal_addr = value_for(&self.specs.internal_addr, record);
|
||||
if src_addr == None || internal_addr == None {
|
||||
return Ok(());
|
||||
}
|
||||
let src_port = value_for(&self.specs.src_port, record);
|
||||
let internal_port = value_for(&self.specs.internal_port, record);
|
||||
let dest_addr = value_for(&self.specs.dest_addr, record);
|
||||
let dest_port = value_for(&self.specs.dest_port, record);
|
||||
singleton_borrow!(self.mappings).put(DnatMapping {
|
||||
src_addr,
|
||||
src_port,
|
||||
internal_addr,
|
||||
internal_port,
|
||||
dest_addr,
|
||||
dest_port,
|
||||
keep_until: Utc::now() + self.specs.keep_duration,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn value_for(spec: &FieldAndValue, record: &Record) -> Option<String> {
|
||||
(&spec.0)
|
||||
.as_deref()
|
||||
.and_then(|s| get_acceptable_key(record, s))
|
||||
.or(spec.1.clone())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::DnatCapture;
|
||||
use crate::domain::test_util::FakeDnatMappings;
|
||||
use crate::domain::{Action, DnatMapping, DnatMappingsPort, ModuleArgs, Record, Value};
|
||||
use crate::{singleton_borrow, singleton_new, singleton_share};
|
||||
use chrono::{Duration, Utc};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "The DnatCapture action needs a log field for the source address in “saddr”"
|
||||
)]
|
||||
fn when_no_saddr_then_error() {
|
||||
let mut args = HashMap::with_capacity(1);
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: Vec::new()
|
||||
});
|
||||
args.insert("addr".to_string(), Value::Str("int_ip".to_string()));
|
||||
let _ = DnatCapture::from_args(args, singleton_share!(mappings));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "The DnatCapture action requires either a field (“addr”) or a value (“addrValue”) for the internal address"
|
||||
)]
|
||||
fn when_no_addr_nor_addr_value_then_error() {
|
||||
let mut args = HashMap::with_capacity(1);
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: Vec::new()
|
||||
});
|
||||
args.insert("saddr".to_string(), Value::Str("src_ip".to_string()));
|
||||
let _ = DnatCapture::from_args(args, singleton_share!(mappings));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_no_addr_but_addr_value_then_no_error() {
|
||||
let mut args = HashMap::with_capacity(2);
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: Vec::new()
|
||||
});
|
||||
args.insert("saddr".to_string(), Value::Str("src_ip".to_string()));
|
||||
args.insert("addrValue".to_string(), Value::Str("1.2.3.4".to_string()));
|
||||
let _ = DnatCapture::from_args(args, singleton_share!(mappings));
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_no_addr_value_but_addr_then_no_error() {
|
||||
let mut args = HashMap::with_capacity(2);
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: Vec::new()
|
||||
});
|
||||
args.insert("saddr".to_string(), Value::Str("src_ip".to_string()));
|
||||
args.insert("addr".to_string(), Value::Str("int_ip".to_string()));
|
||||
let _ = DnatCapture::from_args(args, singleton_share!(mappings));
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_no_keep_seconds_then_63sec() {
|
||||
let mut args = HashMap::with_capacity(2);
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: Vec::new()
|
||||
});
|
||||
args.insert("saddr".to_string(), Value::Str("src_ip".to_string()));
|
||||
args.insert("addr".to_string(), Value::Str("int_ip".to_string()));
|
||||
let action = DnatCapture::from_args(args, singleton_share!(mappings));
|
||||
assert_eq!(Duration::seconds(63), action.specs.keep_duration);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_insufficient_entry_then_no_mapping() {
|
||||
let mut args = HashMap::with_capacity(2);
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: Vec::new()
|
||||
});
|
||||
args.insert("saddr".to_string(), Value::Str("src_ip".to_string()));
|
||||
args.insert("addr".to_string(), Value::Str("int_ip".to_string()));
|
||||
let mut action = DnatCapture::from_args(args, singleton_share!(mappings));
|
||||
action.act(&mut HashMap::new()).unwrap();
|
||||
assert_eq!(0, singleton_borrow!(mappings).mappings.len());
|
||||
}
|
||||
|
||||
fn when_field_and_or_value_then_check_mapping(
|
||||
mut args: ModuleArgs,
|
||||
entry_with_addr: bool,
|
||||
entry_with_daddr: bool,
|
||||
expect: DnatMapping,
|
||||
) {
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: Vec::new()
|
||||
});
|
||||
|
||||
// specify the Action
|
||||
args.insert("saddr".to_string(), Value::Str("sa".to_string()));
|
||||
|
||||
// prepare the entry
|
||||
let mut entry: Record = HashMap::with_capacity(6);
|
||||
entry.insert("sa".to_string(), Value::Str("vsa".to_string()));
|
||||
entry.insert("sp".to_string(), Value::Str("vsp".to_string()));
|
||||
if entry_with_addr {
|
||||
entry.insert("a".to_string(), Value::Str("va".to_string()));
|
||||
entry.insert("p".to_string(), Value::Str("vp".to_string()));
|
||||
}
|
||||
if entry_with_daddr {
|
||||
entry.insert("da".to_string(), Value::Str("vda".to_string()));
|
||||
entry.insert("dp".to_string(), Value::Str("vdp".to_string()));
|
||||
}
|
||||
|
||||
// run
|
||||
let mut action = DnatCapture::from_args(args, singleton_share!(mappings));
|
||||
action.act(&mut entry).unwrap();
|
||||
|
||||
// check the result
|
||||
assert_eq!(1, singleton_borrow!(mappings).get_all().len());
|
||||
let got = singleton_borrow!(mappings)
|
||||
.get_all()
|
||||
.last()
|
||||
.map(|m| {
|
||||
let mut m = (**m).clone();
|
||||
m.keep_until = expect.keep_until;
|
||||
m
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(expect, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_sufficient_record_a_mapping_is_stored() {
|
||||
when_field_and_or_value_then_check_mapping(
|
||||
as_args(vec![("addr", "a")]),
|
||||
true,
|
||||
true,
|
||||
test_dnat_mapping(None, Some("va"), None, None, None),
|
||||
);
|
||||
when_field_and_or_value_then_check_mapping(
|
||||
as_args(vec![("addrValue", "x")]),
|
||||
true,
|
||||
true,
|
||||
test_dnat_mapping(None, Some("x"), None, None, None),
|
||||
);
|
||||
when_field_and_or_value_then_check_mapping(
|
||||
as_args(vec![("addr", "a"), ("addrValue", "x")]),
|
||||
true,
|
||||
true,
|
||||
test_dnat_mapping(None, Some("va"), None, None, None),
|
||||
);
|
||||
when_field_and_or_value_then_check_mapping(
|
||||
as_args(vec![("addr", "a"), ("addrValue", "x")]),
|
||||
false,
|
||||
true,
|
||||
test_dnat_mapping(None, Some("x"), None, None, None),
|
||||
);
|
||||
|
||||
when_field_and_or_value_then_check_mapping(
|
||||
as_args(vec![("addr", "a"), ("daddr", "da")]),
|
||||
true,
|
||||
true,
|
||||
test_dnat_mapping(None, Some("va"), None, Some("vda"), None),
|
||||
);
|
||||
when_field_and_or_value_then_check_mapping(
|
||||
as_args(vec![("addr", "a"), ("daddrValue", "x")]),
|
||||
true,
|
||||
true,
|
||||
test_dnat_mapping(None, Some("va"), None, Some("x"), None),
|
||||
);
|
||||
when_field_and_or_value_then_check_mapping(
|
||||
as_args(vec![("addr", "a"), ("daddr", "da"), ("daddrValue", "x")]),
|
||||
true,
|
||||
true,
|
||||
test_dnat_mapping(None, Some("va"), None, Some("vda"), None),
|
||||
);
|
||||
when_field_and_or_value_then_check_mapping(
|
||||
as_args(vec![("addr", "a"), ("daddr", "da"), ("daddrValue", "x")]),
|
||||
true,
|
||||
false,
|
||||
test_dnat_mapping(None, Some("va"), None, Some("x"), None),
|
||||
);
|
||||
|
||||
when_field_and_or_value_then_check_mapping(
|
||||
as_args(vec![("addr", "a"), ("port", "p")]),
|
||||
true,
|
||||
true,
|
||||
test_dnat_mapping(None, Some("va"), Some("vp"), None, None),
|
||||
);
|
||||
when_field_and_or_value_then_check_mapping(
|
||||
as_args(vec![("addr", "a"), ("dport", "dp")]),
|
||||
true,
|
||||
true,
|
||||
test_dnat_mapping(None, Some("va"), None, None, Some("vdp")),
|
||||
);
|
||||
}
|
||||
|
||||
fn test_dnat_mapping(
|
||||
src_port: Option<&str>,
|
||||
internal_addr: Option<&str>,
|
||||
internal_port: Option<&str>,
|
||||
dest_addr: Option<&str>,
|
||||
dest_port: Option<&str>,
|
||||
) -> DnatMapping {
|
||||
DnatMapping {
|
||||
src_addr: Some("vsa".to_string()),
|
||||
src_port: src_port.map(|s| s.to_string()),
|
||||
internal_addr: internal_addr.map(|s| s.to_string()),
|
||||
internal_port: internal_port.map(|s| s.to_string()),
|
||||
dest_addr: dest_addr.map(|s| s.to_string()),
|
||||
dest_port: dest_port.map(|s| s.to_string()),
|
||||
keep_until: Utc::now(),
|
||||
}
|
||||
}
|
||||
fn as_args(data: Vec<(&str, &str)>) -> ModuleArgs {
|
||||
let mut args = HashMap::with_capacity(data.len());
|
||||
data.iter().for_each(|(f, v)| {
|
||||
args.insert(f.to_string(), Value::Str(v.to_string()));
|
||||
});
|
||||
args
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
use super::{get_acceptable_key, remove_acceptable_key};
|
||||
use crate::domain::{Action, DnatMapping, DnatMappingsPort, ModuleArgs, Record, Singleton, Value};
|
||||
use crate::singleton_borrow;
|
||||
|
||||
type MappingGetter = fn(&DnatMapping) -> &Option<String>;
|
||||
const SADDR_GETTER: MappingGetter = |m| &m.src_addr;
|
||||
const SPORT_GETTER: MappingGetter = |m| &m.src_port;
|
||||
const ADDR_GETTER: MappingGetter = |m| &m.internal_addr;
|
||||
const PORT_GETTER: MappingGetter = |m| &m.internal_port;
|
||||
const DADDR_GETTER: MappingGetter = |m| &m.dest_addr;
|
||||
const DPORT_GETTER: MappingGetter = |m| &m.dest_port;
|
||||
|
||||
type FieldAndGetter = (String, MappingGetter);
|
||||
|
||||
pub struct DnatReplace {
|
||||
mappings: Singleton<dyn DnatMappingsPort>,
|
||||
matchers: Vec<FieldAndGetter>,
|
||||
updaters: Vec<FieldAndGetter>,
|
||||
}
|
||||
|
||||
impl DnatReplace {
|
||||
pub fn from_args(mut args: ModuleArgs, mappings: Singleton<dyn DnatMappingsPort>) -> DnatReplace {
|
||||
let mut matchers = Vec::new();
|
||||
let mut updaters = Vec::new();
|
||||
if let Some(s) = remove_acceptable_key(&mut args, "addr") {
|
||||
matchers.push((s, ADDR_GETTER));
|
||||
}
|
||||
if let Some(s) = remove_acceptable_key(&mut args, "port") {
|
||||
matchers.push((s, PORT_GETTER));
|
||||
}
|
||||
if let Some(s) = remove_acceptable_key(&mut args, "daddr") {
|
||||
matchers.push((s, DADDR_GETTER));
|
||||
}
|
||||
if let Some(s) = remove_acceptable_key(&mut args, "dport") {
|
||||
matchers.push((s, DPORT_GETTER));
|
||||
}
|
||||
if matchers.is_empty() {
|
||||
panic!("The DnatReplace action needs at least one log field on which to do the matching");
|
||||
}
|
||||
updaters.push((
|
||||
remove_acceptable_key(&mut args, "saddrInto")
|
||||
.expect("The DnatReplace action needs a log field to replace in “saddrInto”"),
|
||||
SADDR_GETTER,
|
||||
));
|
||||
if let Some(s) = remove_acceptable_key(&mut args, "sportInto") {
|
||||
updaters.push((s, SPORT_GETTER));
|
||||
}
|
||||
DnatReplace {
|
||||
mappings,
|
||||
matchers,
|
||||
updaters,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Action for DnatReplace {
|
||||
fn act(&mut self, record: &mut Record) -> Result<(), ()> {
|
||||
for (field, _) in self.matchers.iter() {
|
||||
if !record.contains_key(field) {
|
||||
return Ok(()); // not applicable
|
||||
}
|
||||
}
|
||||
for mapping in singleton_borrow!(self.mappings).get_all().iter() {
|
||||
let mut found = true;
|
||||
for (field, getter) in self.matchers.iter() {
|
||||
if &get_acceptable_key(record, field) != getter(*mapping) {
|
||||
found = false; // not matching
|
||||
break;
|
||||
}
|
||||
}
|
||||
if found {
|
||||
for (field, getter) in self.updaters.iter() {
|
||||
if let Some(s) = getter(mapping) {
|
||||
record.insert(field.clone(), Value::Str(s.clone()));
|
||||
}
|
||||
}
|
||||
return Ok(()); // replacement done; stop here
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::DnatReplace;
|
||||
use crate::domain::test_util::FakeDnatMappings;
|
||||
use crate::domain::{Action, DnatMapping, Value};
|
||||
use crate::{singleton_new, singleton_share};
|
||||
use chrono::{Duration, Utc};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "The DnatReplace action needs a log field to replace in “saddrInto”")]
|
||||
fn when_no_saddrinto_then_error() {
|
||||
let mut args = HashMap::with_capacity(1);
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: Vec::new()
|
||||
});
|
||||
args.insert("addr".to_string(), Value::Str("int_ip".to_string()));
|
||||
let _ = DnatReplace::from_args(args, singleton_share!(mappings));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "The DnatReplace action needs at least one log field on which to do the matching"
|
||||
)]
|
||||
fn when_no_match_field_then_error() {
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: Vec::new()
|
||||
});
|
||||
let mut args = HashMap::with_capacity(1);
|
||||
args.insert("saddrInto".to_string(), Value::Str("src_ip".to_string()));
|
||||
let _ = DnatReplace::from_args(args, singleton_share!(mappings));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_saddrinto_and_at_least_one_match_field_then_no_error() {
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: Vec::new()
|
||||
});
|
||||
let mut args = HashMap::with_capacity(2);
|
||||
args.insert("saddrInto".to_string(), Value::Str("src_ip".to_string()));
|
||||
args.insert("dport".to_string(), Value::Int(1234));
|
||||
let action = DnatReplace::from_args(args, singleton_share!(mappings));
|
||||
assert_eq!(
|
||||
vec!(("1234".to_string(), Some("dp".to_string()))),
|
||||
action
|
||||
.matchers
|
||||
.iter()
|
||||
.map(|(f, g)| (f.clone(), g(&mapping_getter_identification()).clone()))
|
||||
.collect::<Vec<(String, Option<String>)>>()
|
||||
);
|
||||
assert_eq!(
|
||||
vec!(("src_ip".to_string(), Some("sa".to_string()))),
|
||||
action
|
||||
.updaters
|
||||
.iter()
|
||||
.map(|(f, g)| (f.clone(), g(&mapping_getter_identification()).clone()))
|
||||
.collect::<Vec<(String, Option<String>)>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_no_matching_entry_then_no_change() {
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: vec!(sample_dnat_mapping()),
|
||||
});
|
||||
let mut args = HashMap::with_capacity(2);
|
||||
args.insert("saddrInto".to_string(), Value::Str("src_ip".to_string()));
|
||||
args.insert("port".to_string(), Value::Str("src_port".to_string()));
|
||||
let mut record = HashMap::new();
|
||||
record.insert("src_ip".to_string(), Value::Str("prox".to_string()));
|
||||
record.insert("dest_ip".to_string(), Value::Str("serv".to_string()));
|
||||
let expected = record.clone();
|
||||
let mut action = DnatReplace::from_args(args, singleton_share!(mappings));
|
||||
action.act(&mut record).unwrap();
|
||||
assert_eq!(expected, record);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_no_matching_value_then_no_change() {
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: vec!(sample_dnat_mapping()),
|
||||
});
|
||||
let mut args = HashMap::with_capacity(2);
|
||||
args.insert("saddrInto".to_string(), Value::Str("src_ip".to_string()));
|
||||
args.insert("port".to_string(), Value::Str("src_port".to_string()));
|
||||
let mut record = HashMap::with_capacity(3);
|
||||
record.insert("src_ip".to_string(), Value::Str("prox".to_string()));
|
||||
record.insert("src_port".to_string(), Value::Str("1234".to_string()));
|
||||
record.insert("dest_ip".to_string(), Value::Str("serv".to_string()));
|
||||
let expected = record.clone();
|
||||
let mut action = DnatReplace::from_args(args, singleton_share!(mappings));
|
||||
action.act(&mut record).unwrap();
|
||||
assert_eq!(expected, record);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_matching_entry_then_change() {
|
||||
let mappings = singleton_new!(FakeDnatMappings {
|
||||
mappings: vec!(sample_dnat_mapping()),
|
||||
});
|
||||
let mut args = HashMap::with_capacity(2);
|
||||
args.insert("saddrInto".to_string(), Value::Str("src_ip".to_string()));
|
||||
args.insert("port".to_string(), Value::Str("src_port".to_string()));
|
||||
let mut record = HashMap::with_capacity(3);
|
||||
record.insert("src_ip".to_string(), Value::Str("prox".to_string()));
|
||||
record.insert("src_port".to_string(), Value::Int(12345));
|
||||
record.insert("dest_ip".to_string(), Value::Str("serv".to_string()));
|
||||
let mut action = DnatReplace::from_args(args, singleton_share!(mappings));
|
||||
action.act(&mut record).unwrap();
|
||||
assert_eq!(3, record.len());
|
||||
assert_eq!(Some(&Value::Str("bad".to_string())), record.get("src_ip"));
|
||||
assert_eq!(Some(&Value::Int(12345)), record.get("src_port"));
|
||||
assert_eq!(Some(&Value::Str("serv".to_string())), record.get("dest_ip"));
|
||||
}
|
||||
|
||||
fn mapping_getter_identification() -> DnatMapping {
|
||||
DnatMapping {
|
||||
src_addr: Some("sa".to_string()),
|
||||
src_port: Some("sp".to_string()),
|
||||
internal_addr: Some("ia".to_string()),
|
||||
internal_port: Some("ip".to_string()),
|
||||
dest_addr: Some("da".to_string()),
|
||||
dest_port: Some("dp".to_string()),
|
||||
keep_until: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_dnat_mapping() -> DnatMapping {
|
||||
DnatMapping {
|
||||
src_addr: Some("bad".to_string()),
|
||||
src_port: None,
|
||||
internal_addr: Some("prox".to_string()),
|
||||
internal_port: Some("12345".to_string()),
|
||||
dest_addr: Some("serv".to_string()),
|
||||
dest_port: None,
|
||||
keep_until: Utc::now() + Duration::hours(1),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,16 @@ mod counter_raise;
|
|||
pub use self::counter_raise::*;
|
||||
mod counter_reset;
|
||||
pub use self::counter_reset::*;
|
||||
mod dnat_capture;
|
||||
pub use self::dnat_capture::*;
|
||||
mod dnat_replace;
|
||||
pub use self::dnat_replace::*;
|
||||
mod log;
|
||||
pub use self::log::*;
|
||||
mod noop;
|
||||
pub use self::noop::*;
|
||||
|
||||
use crate::domain::{Counters, CountersPort, ModuleArgs, Singleton, Value};
|
||||
use crate::domain::{Counters, CountersPort, ModuleArgs, Record, Singleton, Value};
|
||||
use chrono::Duration;
|
||||
|
||||
pub struct CounterAction<C: CountersPort> {
|
||||
|
@ -52,9 +56,17 @@ impl<C: CountersPort> CounterAction<C> {
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_acceptable_key(args: &mut ModuleArgs, key: &str) -> Option<String> {
|
||||
pub fn get_acceptable_key<'r, 'k>(record: &'r Record, key: &'k str) -> Option<String> {
|
||||
match record.get(key) {
|
||||
Some(&Value::Str(ref s)) => Some(s.clone()),
|
||||
Some(&Value::Int(ref i)) => Some(format!("{}", i)),
|
||||
Some(&Value::Date(ref d)) => Some(format!("{}", d.timestamp())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_acceptable_key(args: &mut ModuleArgs, key: &str) -> Option<String> {
|
||||
match args.remove(key) {
|
||||
None => None,
|
||||
Some(Value::Str(s)) => Some(s),
|
||||
Some(Value::Int(i)) => Some(format!("{}", i)),
|
||||
Some(Value::Date(d)) => Some(format!("{}", d.timestamp())),
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
use crate::domain::Value;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub type CounterRef<'t> = (&'t str, &'t Value);
|
||||
pub type CounterData = (usize, Option<DateTime<Utc>>);
|
||||
|
||||
pub trait CountersPort {
|
||||
fn modify(
|
||||
&mut self,
|
||||
entry: CounterRef,
|
||||
data: CounterData,
|
||||
f: impl FnMut(&mut CounterData, CounterData) -> usize,
|
||||
) -> usize;
|
||||
fn remove(&mut self, entry: CounterRef) -> Option<CounterData>;
|
||||
fn remove_if(&mut self, predicate: impl Fn(&CounterData) -> bool);
|
||||
}
|
||||
|
||||
pub struct Counters<P: CountersPort> {
|
||||
backend: P,
|
||||
}
|
||||
|
||||
impl<P: CountersPort> Counters<P> {
|
||||
pub fn new<X: CountersPort>(backend: X) -> Counters<X> {
|
||||
Counters { backend }
|
||||
}
|
||||
|
||||
fn grace_active(data: &CounterData) -> bool {
|
||||
if let Some(dt) = data.1 {
|
||||
data.0 == 0 && dt > Utc::now()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn clean(&mut self) {
|
||||
let now = Utc::now();
|
||||
self.backend.remove_if(|c_data| {
|
||||
if let (_, Some(dt)) = c_data {
|
||||
let ref_now = &now;
|
||||
return dt <= ref_now;
|
||||
}
|
||||
false
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set(&mut self, entry: CounterRef, data: CounterData) -> usize {
|
||||
self.clean();
|
||||
self.backend.modify(entry, data, |value, data| {
|
||||
*value = data;
|
||||
(*value).0
|
||||
})
|
||||
}
|
||||
|
||||
pub fn augment(&mut self, entry: CounterRef, data: CounterData) -> usize {
|
||||
self.clean();
|
||||
self.backend.modify(entry, data, |value, data| {
|
||||
if !Counters::<P>::grace_active(&value) {
|
||||
(*value).0 = (*value).0 + data.0;
|
||||
if let Some(wanted_dt) = data.1 {
|
||||
match value.1 {
|
||||
Some(existing_dt) if existing_dt < wanted_dt => value.1 = data.1,
|
||||
None => value.1 = data.1,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
(*value).0
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, entry: CounterRef, grace_until: Option<DateTime<Utc>>) -> usize {
|
||||
self.clean();
|
||||
match grace_until {
|
||||
Some(_) => {
|
||||
// a grace-time is wanted, so the entry must exist…
|
||||
self.backend.modify(entry, (0, grace_until), |value, data| {
|
||||
match value {
|
||||
// … and its grace-time is set to the farther value between existing and requested
|
||||
(0, Some(existing_dt)) if *existing_dt > data.1.unwrap() => (),
|
||||
_ => (*value) = data,
|
||||
};
|
||||
0
|
||||
})
|
||||
}
|
||||
None => {
|
||||
// no grace-time wanted, so the entry is deleted…
|
||||
if let Some((0, Some(existing_dt))) = self.backend.remove(entry) {
|
||||
// … unless an existing grace-time was found
|
||||
self
|
||||
.backend
|
||||
.modify(entry, (0, Some(existing_dt)), |value, data| {
|
||||
*value = data;
|
||||
0
|
||||
})
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::test_util::FakeCountersAdapter;
|
||||
use crate::domain::{CounterData, Counters, Singleton, Value};
|
||||
use crate::{singleton_borrow, singleton_new, singleton_share};
|
||||
use chrono::{Duration, Utc};
|
||||
use std::collections::HashMap;
|
||||
use std::{thread, time};
|
||||
|
||||
#[test]
|
||||
fn set_forces_the_value_of_a_counter() {
|
||||
let (counters_store, mut counters) = get_store_counters();
|
||||
let (c_ref, stored_key) = get_ref_and_key("test", &Value::Int(5));
|
||||
let value = counters.set(c_ref, (9, None));
|
||||
assert_eq!(value, 9);
|
||||
let stored_value = singleton_borrow!(counters_store)
|
||||
.get_mut(&stored_key)
|
||||
.unwrap()
|
||||
.0;
|
||||
assert_eq!(stored_value, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_counter_starts_from_0() {
|
||||
let (counters_store, mut counters) = get_store_counters();
|
||||
let (_, stored_key) = get_ref_and_key("test", &Value::Bool(true));
|
||||
let stored_value = singleton_borrow!(counters_store)
|
||||
.get_mut(&stored_key)
|
||||
.map(|_| 0);
|
||||
assert_eq!(stored_value, None);
|
||||
let value = counters.augment(("test", &Value::Bool(true)), (1, None));
|
||||
assert_eq!(value, 1);
|
||||
let stored_value = singleton_borrow!(counters_store)
|
||||
.get_mut(&stored_key)
|
||||
.unwrap()
|
||||
.0;
|
||||
assert_eq!(stored_value, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn augment_raises_a_counter_by_its_amount() {
|
||||
let (_, mut counters) = get_store_counters();
|
||||
let str_value = Value::Str("string".to_string());
|
||||
counters.set(("test", &str_value), (4, None));
|
||||
let value = counters.augment(("test", &str_value), (3, None));
|
||||
assert_eq!(value, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_without_gracetime_removes_a_counter() {
|
||||
let (counters_store, mut counters) = get_store_counters();
|
||||
let now = Utc::now();
|
||||
let date_value = Value::Date(now.clone());
|
||||
let (c_ref, stored_key) = get_ref_and_key("test", &date_value);
|
||||
counters.augment(c_ref, (5, None));
|
||||
let stored_value = singleton_borrow!(counters_store)
|
||||
.get_mut(&stored_key)
|
||||
.unwrap()
|
||||
.0;
|
||||
assert_eq!(stored_value, 5);
|
||||
let value = counters.reset(("test", &Value::Date(now)), None);
|
||||
assert_eq!(value, 0);
|
||||
let stored_value = singleton_borrow!(counters_store)
|
||||
.get_mut(&stored_key)
|
||||
.map(|_| 0);
|
||||
assert_eq!(stored_value, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn augment_records_the_longest_datetime() {
|
||||
let (counters_store, mut counters) = get_store_counters();
|
||||
let (c_ref, stored_key) = get_ref_and_key("test", &Value::Bool(true));
|
||||
let old_dt = Utc::now() + Duration::minutes(1);
|
||||
let new_dt = Utc::now() + Duration::minutes(1);
|
||||
assert!(old_dt < new_dt);
|
||||
counters.augment(c_ref.clone(), (1, Some(new_dt)));
|
||||
counters.augment(c_ref, (3, Some(old_dt)));
|
||||
let stored_dt = singleton_borrow!(counters_store)
|
||||
.get_mut(&stored_key)
|
||||
.unwrap()
|
||||
.1
|
||||
.unwrap();
|
||||
assert_eq!(stored_dt, new_dt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn augment_without_timeout_is_ignored_in_the_presence_of_a_gracetime() {
|
||||
let (counters_store, mut counters) = get_store_counters();
|
||||
let (c_ref, stored_key) = get_ref_and_key("test", &Value::Bool(true));
|
||||
let future_dt = Utc::now() + Duration::days(1);
|
||||
counters.reset(c_ref.clone(), Some(future_dt));
|
||||
let value = counters.augment(c_ref, (3, None));
|
||||
assert_eq!(value, 0);
|
||||
assert_eq!(
|
||||
&(0 as usize, Some(future_dt)),
|
||||
singleton_borrow!(counters_store)
|
||||
.get_mut(&stored_key)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn augment_with_timeout_is_ignored_in_the_presence_of_a_gracetime() {
|
||||
let (counters_store, mut counters) = get_store_counters();
|
||||
let (c_ref, stored_key) = get_ref_and_key("test", &Value::Bool(true));
|
||||
let future_dt = Utc::now() + Duration::days(1);
|
||||
let soon_dt = Utc::now() + Duration::hours(1);
|
||||
counters.reset(c_ref.clone(), Some(future_dt));
|
||||
let value = counters.augment(c_ref, (3, Some(soon_dt)));
|
||||
assert_eq!(value, 0);
|
||||
assert_eq!(
|
||||
&(0 as usize, Some(future_dt)),
|
||||
singleton_borrow!(counters_store)
|
||||
.get_mut(&stored_key)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn augment_also_cleans_obsolete_counters() {
|
||||
let (counters_store, mut counters) = get_store_counters();
|
||||
let (c_ref, stored_key) = get_ref_and_key("test", &Value::Bool(true));
|
||||
let future_dt = Utc::now() + Duration::milliseconds(500);
|
||||
counters.augment(c_ref, (3, Some(future_dt)));
|
||||
assert_eq!(1, singleton_borrow!(counters_store).len());
|
||||
assert_eq!(
|
||||
3,
|
||||
singleton_borrow!(counters_store)
|
||||
.get_mut(&stored_key)
|
||||
.unwrap()
|
||||
.0
|
||||
);
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
let (c_ref, stored_key) = get_ref_and_key("test2", &Value::Bool(false));
|
||||
let value = counters.augment(c_ref, (5, None));
|
||||
assert_eq!(value, 5);
|
||||
assert_eq!(1, singleton_borrow!(counters_store).len());
|
||||
assert_eq!(
|
||||
5,
|
||||
singleton_borrow!(counters_store)
|
||||
.get_mut(&stored_key)
|
||||
.unwrap()
|
||||
.0
|
||||
);
|
||||
}
|
||||
|
||||
fn get_store_counters() -> (
|
||||
Singleton<HashMap<(String, Value), CounterData>>,
|
||||
Counters<FakeCountersAdapter>,
|
||||
) {
|
||||
let counters_store = singleton_new!(HashMap::new());
|
||||
let counters = Counters::<FakeCountersAdapter>::new(FakeCountersAdapter {
|
||||
counters: singleton_share!(&counters_store),
|
||||
});
|
||||
(counters_store, counters)
|
||||
}
|
||||
|
||||
fn get_ref_and_key<'t>(s: &'t str, v: &'t Value) -> ((&'t str, &'t Value), (String, Value)) {
|
||||
let storage_key = (s.to_string(), v.clone());
|
||||
let key_ref = (s, v);
|
||||
(key_ref, storage_key)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct DnatMapping {
|
||||
pub src_addr: Option<String>,
|
||||
pub src_port: Option<String>,
|
||||
pub internal_addr: Option<String>,
|
||||
pub internal_port: Option<String>,
|
||||
pub dest_addr: Option<String>,
|
||||
pub dest_port: Option<String>,
|
||||
pub keep_until: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub trait DnatMappingsPort {
|
||||
fn put(&mut self, mapping: DnatMapping);
|
||||
fn get_all(&mut self) -> Vec<&DnatMapping>;
|
||||
}
|
|
@ -3,14 +3,16 @@ pub mod filter;
|
|||
|
||||
mod config;
|
||||
pub use self::config::*;
|
||||
mod counter;
|
||||
pub use self::counter::*;
|
||||
mod dnat;
|
||||
pub use self::dnat::*;
|
||||
mod log;
|
||||
pub use self::log::*;
|
||||
mod module;
|
||||
pub use self::module::*;
|
||||
mod workflow;
|
||||
pub use self::workflow::*;
|
||||
mod counter;
|
||||
pub use self::counter::*;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::collections::HashMap;
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use crate::domain::{Action, CounterData, CounterRef, CountersPort, Filter, LogMessage, LogPort, Record, Singleton, Value};
|
||||
use crate::domain::{
|
||||
Action, CounterData, CounterRef, CountersPort, DnatMapping, DnatMappingsPort, Filter, LogMessage,
|
||||
LogPort, Record, Singleton, Value,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const ACT_NAME: &str = "fake_action";
|
||||
|
@ -92,3 +95,15 @@ impl CountersPort for FakeCountersAdapter {
|
|||
singleton_borrow!(self.counters).retain(|_, v| !predicate(v));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FakeDnatMappings {
|
||||
pub mappings: Vec<DnatMapping>,
|
||||
}
|
||||
impl DnatMappingsPort for FakeDnatMappings {
|
||||
fn put(&mut self, mapping: DnatMapping) {
|
||||
self.mappings.push(mapping);
|
||||
}
|
||||
fn get_all(&mut self) -> Vec<&DnatMapping> {
|
||||
self.mappings.iter().collect()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
use crate::domain::{CounterData, CounterRef, CountersPort, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
type CounterKeys = HashMap<Value, CounterData>;
|
||||
pub struct InMemoryCounterAdapter {
|
||||
counters: HashMap<String, CounterKeys>,
|
||||
}
|
||||
|
||||
impl InMemoryCounterAdapter {
|
||||
pub fn new() -> Self {
|
||||
InMemoryCounterAdapter {
|
||||
counters: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CountersPort for InMemoryCounterAdapter {
|
||||
fn modify(
|
||||
&mut self,
|
||||
entry: CounterRef,
|
||||
data: CounterData,
|
||||
mut f: impl FnMut(&mut CounterData, CounterData) -> usize,
|
||||
) -> usize {
|
||||
if !self.counters.contains_key(entry.0) {
|
||||
self.counters.insert(entry.0.to_string(), HashMap::new());
|
||||
}
|
||||
let keys = self.counters.get_mut(entry.0).unwrap();
|
||||
if !keys.contains_key(entry.1) {
|
||||
keys.insert(entry.1.clone(), (0, None));
|
||||
}
|
||||
f(keys.get_mut(entry.1).unwrap(), data)
|
||||
}
|
||||
|
||||
fn remove(&mut self, entry: CounterRef) -> Option<CounterData> {
|
||||
let (to_remove, option) = match self.counters.get_mut(entry.0) {
|
||||
None => (false, None),
|
||||
Some(keys) => match keys.remove(entry.1) {
|
||||
None => (false, None),
|
||||
Some(d) => (keys.is_empty(), Some(d)),
|
||||
},
|
||||
};
|
||||
if to_remove {
|
||||
self.counters.remove(entry.0);
|
||||
}
|
||||
option
|
||||
}
|
||||
|
||||
fn remove_if(&mut self, predicate: impl Fn(&CounterData) -> bool) {
|
||||
self.counters.retain(|_, name| {
|
||||
name.retain(|_, data| !predicate(data));
|
||||
!name.is_empty()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{CounterKeys, InMemoryCounterAdapter};
|
||||
use crate::domain::{CountersPort, Value};
|
||||
use chrono::Utc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn modify_allows_modifying_an_entry_and_returns_the_new_value() {
|
||||
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
|
||||
let counter = "counter";
|
||||
let key = Value::Str("1.2.3.4".to_string());
|
||||
let new_data = (2, None);
|
||||
counters.insert(counter.to_string(), HashMap::new());
|
||||
counters
|
||||
.get_mut(counter)
|
||||
.unwrap()
|
||||
.insert(key.clone(), (1, Some(Utc::now())));
|
||||
let mut adapter = InMemoryCounterAdapter { counters };
|
||||
let new_value = adapter.modify((counter, &key), new_data.clone(), |existing, new| {
|
||||
*existing = new;
|
||||
(*existing).0
|
||||
});
|
||||
assert_eq!(2, new_value);
|
||||
assert_eq!(
|
||||
&(2 as usize, None),
|
||||
adapter.counters.get(counter).unwrap().get(&key).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_remove_the_entry_is_not_there() {
|
||||
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
|
||||
let counter = "counter";
|
||||
let key1 = Value::Str("1.2.3.4".to_string());
|
||||
let key2 = Value::Bool(true);
|
||||
let data1 = (2, None);
|
||||
let data2 = (5, None);
|
||||
counters.insert(counter.to_string(), HashMap::new());
|
||||
let map = counters.get_mut(counter).unwrap();
|
||||
map.insert(key1.clone(), data1);
|
||||
map.insert(key2.clone(), data2);
|
||||
let mut adapter = InMemoryCounterAdapter { counters };
|
||||
let removed = adapter.remove((counter, &key1));
|
||||
assert_eq!(Some(data1), removed);
|
||||
assert!(!adapter.counters.get(counter).unwrap().contains_key(&key1));
|
||||
assert_eq!(
|
||||
&data2,
|
||||
adapter.counters.get(counter).unwrap().get(&key2).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_on_unexisting_entry_does_nothing_and_returns_none() {
|
||||
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
|
||||
let counter = "counter";
|
||||
let key1 = Value::Str("1.2.3.4".to_string());
|
||||
let key2 = Value::Bool(true);
|
||||
let data1 = (2, None);
|
||||
counters.insert(counter.to_string(), HashMap::new());
|
||||
let map = counters.get_mut(counter).unwrap();
|
||||
map.insert(key1.clone(), data1);
|
||||
let mut adapter = InMemoryCounterAdapter { counters };
|
||||
let removed = adapter.remove((counter, &key2));
|
||||
assert_eq!(None, removed);
|
||||
assert!(adapter.counters.get(counter).unwrap().contains_key(&key1));
|
||||
assert_eq!(
|
||||
&data1,
|
||||
adapter.counters.get(counter).unwrap().get(&key1).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_last_key_is_removed_by_remove_counter_is_also_removed() {
|
||||
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
|
||||
let counter = "counter";
|
||||
let key1 = Value::Str("1.2.3.4".to_string());
|
||||
let key2 = Value::Bool(true);
|
||||
let data1 = (2, None);
|
||||
let data2 = (5, None);
|
||||
counters.insert(counter.to_string(), HashMap::new());
|
||||
let map = counters.get_mut(counter).unwrap();
|
||||
map.insert(key1.clone(), data1);
|
||||
map.insert(key2.clone(), data2);
|
||||
let mut adapter = InMemoryCounterAdapter { counters };
|
||||
assert_eq!(Some(data1), adapter.remove((counter, &key1)));
|
||||
assert_eq!(Some(data2), adapter.remove((counter, &key2)));
|
||||
assert!(!adapter.counters.contains_key(counter));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removeif_removes_entries_that_match_the_predicate() {
|
||||
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
|
||||
let counter = "counter";
|
||||
let key1 = Value::Str("1.2.3.4".to_string());
|
||||
let key2 = Value::Bool(true);
|
||||
let data1 = (2, None);
|
||||
let data2 = (5, None);
|
||||
counters.insert(counter.to_string(), HashMap::new());
|
||||
let map = counters.get_mut(counter).unwrap();
|
||||
map.insert(key1.clone(), data1);
|
||||
map.insert(key2.clone(), data2);
|
||||
let mut adapter = InMemoryCounterAdapter { counters };
|
||||
adapter.remove_if(|(u, _)| *u == 2);
|
||||
assert_eq!(1, adapter.counters.get(counter).unwrap().len());
|
||||
assert_eq!(
|
||||
Some(&(5 as usize, None)),
|
||||
adapter.counters.get(counter).unwrap().get(&key2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn after_last_key_is_removed_by_removeif_counter_is_also_removed() {
|
||||
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
|
||||
let counter = "counter";
|
||||
let key1 = Value::Str("1.2.3.4".to_string());
|
||||
let data1 = (2, None);
|
||||
counters.insert(counter.to_string(), HashMap::new());
|
||||
let map = counters.get_mut(counter).unwrap();
|
||||
map.insert(key1.clone(), data1);
|
||||
let mut adapter = InMemoryCounterAdapter { counters };
|
||||
adapter.remove_if(|(u, _)| *u == 2);
|
||||
assert_eq!(0, adapter.counters.len());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
use crate::domain::{DnatMapping, DnatMappingsPort};
|
||||
use chrono::Utc;
|
||||
|
||||
pub struct InMemoryDnatMappingsAdapter {
|
||||
mappings: Vec<DnatMapping>,
|
||||
}
|
||||
|
||||
impl InMemoryDnatMappingsAdapter {
|
||||
pub fn new() -> Self {
|
||||
InMemoryDnatMappingsAdapter {
|
||||
mappings: Vec::new(),
|
||||
}
|
||||
}
|
||||
fn clean(&mut self) {
|
||||
let now = Utc::now();
|
||||
self.mappings.retain(|m| m.keep_until > now);
|
||||
}
|
||||
}
|
||||
|
||||
impl DnatMappingsPort for InMemoryDnatMappingsAdapter {
|
||||
fn put(&mut self, mapping: DnatMapping) {
|
||||
self.clean();
|
||||
self.mappings.push(mapping);
|
||||
}
|
||||
fn get_all(&mut self) -> Vec<&DnatMapping> {
|
||||
self.clean();
|
||||
self.mappings.iter().collect()
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod config;
|
||||
pub mod counter;
|
||||
pub mod dnat;
|
||||
pub mod log;
|
||||
|
|
35
src/main.rs
35
src/main.rs
|
@ -3,14 +3,16 @@ mod domain;
|
|||
mod infra;
|
||||
mod service;
|
||||
|
||||
use crate::domain::action::{CounterRaise, CounterReset, Log, Noop};
|
||||
use crate::domain::filter::Equals;
|
||||
use crate::domain::{ConfigPort, Counters, Modules, Workflow};
|
||||
use crate::infra::config::ConfFile;
|
||||
use crate::infra::counter::InMemoryCounterAdapter;
|
||||
use crate::infra::log::SystemdLogAdapter;
|
||||
use domain::action::{CounterRaise, CounterReset, DnatCapture, DnatReplace, Log, Noop};
|
||||
use domain::filter::Equals;
|
||||
use domain::{ConfigPort, Counters, Modules, Workflow};
|
||||
use infra::config::ConfFile;
|
||||
use infra::counter::InMemoryCounterAdapter;
|
||||
use infra::dnat::InMemoryDnatMappingsAdapter;
|
||||
use infra::log::SystemdLogAdapter;
|
||||
|
||||
type CountersImpl = InMemoryCounterAdapter;
|
||||
type DnatImpl = InMemoryDnatMappingsAdapter;
|
||||
type LogImpl = SystemdLogAdapter;
|
||||
|
||||
fn main() {
|
||||
|
@ -18,6 +20,7 @@ fn main() {
|
|||
let log = singleton_new!(LogImpl::open().expect("Error initializing systemd"));
|
||||
let mut modules = Modules::new();
|
||||
let counters = singleton_new!(Counters::<CountersImpl>::new(CountersImpl::new()));
|
||||
let dnat = singleton_new!(DnatImpl::new());
|
||||
let gets_moved_into_closure = singleton_share!(counters);
|
||||
modules.register_action(
|
||||
"action_counterRaise".to_string(),
|
||||
|
@ -38,6 +41,26 @@ fn main() {
|
|||
))
|
||||
}),
|
||||
);
|
||||
let gets_moved_into_closure = singleton_share!(dnat);
|
||||
modules.register_action(
|
||||
"action_dnatCapture".to_string(),
|
||||
Box::new(move |a| {
|
||||
Box::new(DnatCapture::from_args(
|
||||
a,
|
||||
singleton_share!(gets_moved_into_closure), // clone for each call of the constructor
|
||||
))
|
||||
}),
|
||||
);
|
||||
let gets_moved_into_closure = singleton_share!(dnat);
|
||||
modules.register_action(
|
||||
"action_dnatReplace".to_string(),
|
||||
Box::new(move |a| {
|
||||
Box::new(DnatReplace::from_args(
|
||||
a,
|
||||
singleton_share!(gets_moved_into_closure), // clone for each call of the constructor
|
||||
))
|
||||
}),
|
||||
);
|
||||
modules.register_action(
|
||||
"action_log".to_string(),
|
||||
Box::new(move |a| Box::new(Log::from_args(a, singleton_share!(log)))),
|
||||
|
|
Loading…
Reference in New Issue