maybenot_machines/
lib.rs

1use std::str::FromStr;
2
3use anyhow::{Result, bail};
4use enum_map::enum_map;
5use maybenot::{Machine, state::State};
6use rand_core::RngCore;
7use serde::{Deserialize, Serialize};
8
9pub mod break_pad;
10pub mod front;
11pub mod interspace;
12pub mod netflow;
13pub mod regulator;
14pub mod scrambler;
15pub mod tamaraw;
16
17/// Static machines are hardcoded machines. They are used for testing, as a
18/// starting point for generating new machines, or as part of defenses.
19#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
20pub enum StaticMachine {
21    /// A minimal machine that does nothing but is still a valid machine.
22    NoOp,
23    /// A simple machine that infrequently sends packets with the goal of
24    /// keeping NetFlow records coarse.
25    SimpleNetFlow,
26    /// Client-side machines for the RegulaTor defense.
27    RegulatorClient {
28        /// download-upload packet ratio
29        u: f64,
30        /// delay cap (s)
31        c: f64,
32    },
33    /// Server-side machines for the RegulaTor defense.
34    RegulatorServer {
35        /// initial surge rate
36        r: f64,
37        /// packet sending decay rate
38        d: f64,
39        /// surge threshold ratio
40        t: f64,
41        /// padding budget
42        n: f64,
43        /// number of buckets for distribution approximation
44        b: usize,
45    },
46    /// Tamaraw defense.
47    Tamaraw {
48        /// padding rate in s/packet
49        p: f64,
50        /// the duration (in microseconds) that stops Tamaraw if no normal
51        /// packet has been sent within
52        stop_window: f64,
53    },
54    // Client-side machine for Interspace.
55    InterspaceClient,
56    /// Server-side machine for Interspace.
57    InterspaceServer,
58    /// Machines for the FRONT defense.
59    Front {
60        /// max padding budget to sample
61        padding_budget_max: u32,
62        /// min of sampled padding window
63        window_min: f64,
64        /// max of sampled padding window
65        window_max: f64,
66        /// number of states for distribution approximation
67        num_states: usize,
68    },
69    /// Client-side machine for BreakPad.
70    BreakPadClient,
71    /// Server-side machine for BreakPad.
72    BreakPadServer,
73    /// Client-side machine for Scrambler.
74    ScramblerClient,
75    /// Server-side machines for Scrambler.
76    ScramblerServer {
77        /// padding frequency / interval
78        interval: f64,
79        /// minimum number of packets to send per burst
80        min_count: f64,
81        /// min of sampled trail padding
82        min_trail: f64,
83        /// max of sampled trail padding
84        max_trail: f64,
85    },
86}
87
88impl FromStr for StaticMachine {
89    type Err = anyhow::Error;
90
91    fn from_str(s: &str) -> Result<Self> {
92        if s.contains("tamaraw ") {
93            let parts: Vec<&str> = s.split_whitespace().collect();
94            if parts.len() != 3 {
95                bail!("invalid tamaraw defense: {}", s);
96            }
97            let p = parts[1].parse::<f64>().map_err(|_| {
98                anyhow::anyhow!("invalid tamaraw defense parameter 'p': {}", parts[1])
99            })?;
100            let stop_window = parts[2].parse::<f64>().map_err(|_| {
101                anyhow::anyhow!(
102                    "invalid tamaraw defense parameter 'stop_window': {}",
103                    parts[2]
104                )
105            })?;
106            return Ok(StaticMachine::Tamaraw { p, stop_window });
107        }
108
109        if s.contains("regulator_client ") {
110            let parts: Vec<&str> = s.split_whitespace().collect();
111            if parts.len() != 3 {
112                bail!("invalid regulator client defense: {}", s);
113            }
114            let u = parts[1].parse::<f64>().map_err(|_| {
115                anyhow::anyhow!(
116                    "invalid regulator client defense parameter 'u': {}",
117                    parts[1]
118                )
119            })?;
120            let c = parts[2].parse::<f64>().map_err(|_| {
121                anyhow::anyhow!(
122                    "invalid regulator client defense parameter 'c': {}",
123                    parts[2]
124                )
125            })?;
126            return Ok(StaticMachine::RegulatorClient { u, c });
127        }
128        if s.contains("regulator_server ") {
129            let parts: Vec<&str> = s.split_whitespace().collect();
130            if parts.len() != 6 {
131                bail!("invalid regulator server defense: {}", s);
132            }
133            let r = parts[1].parse::<f64>().map_err(|_| {
134                anyhow::anyhow!(
135                    "invalid regulator server defense parameter 'r': {}",
136                    parts[1]
137                )
138            })?;
139            let d = parts[2].parse::<f64>().map_err(|_| {
140                anyhow::anyhow!(
141                    "invalid regulator server defense parameter 'd': {}",
142                    parts[2]
143                )
144            })?;
145            let t = parts[3].parse::<f64>().map_err(|_| {
146                anyhow::anyhow!(
147                    "invalid regulator server defense parameter 't': {}",
148                    parts[3]
149                )
150            })?;
151            let n = parts[4].parse::<f64>().map_err(|_| {
152                anyhow::anyhow!(
153                    "invalid regulator server defense parameter 'n': {}",
154                    parts[4]
155                )
156            })?;
157            let b = parts[5].parse::<usize>().map_err(|_| {
158                anyhow::anyhow!(
159                    "invalid regulator server defense parameter 'b': {}",
160                    parts[5]
161                )
162            })?;
163            return Ok(StaticMachine::RegulatorServer { r, d, t, n, b });
164        }
165
166        if s.contains("front ") {
167            let parts: Vec<&str> = s.split_whitespace().collect();
168            if parts.len() != 5 {
169                bail!("invalid front defense: {}", s);
170            }
171            let padding_budget_max = parts[1].parse::<u32>().map_err(|_| {
172                anyhow::anyhow!(
173                    "invalid front defense parameter 'padding_budget_max': {}",
174                    parts[1]
175                )
176            })?;
177            let window_min = parts[2].parse::<f64>().map_err(|_| {
178                anyhow::anyhow!("invalid front defense parameter 'window_min': {}", parts[2])
179            })?;
180            let window_max = parts[3].parse::<f64>().map_err(|_| {
181                anyhow::anyhow!("invalid front defense parameter 'window_max': {}", parts[3])
182            })?;
183            let num_states = parts[4].parse::<usize>().map_err(|_| {
184                anyhow::anyhow!("invalid front defense parameter 'num_states': {}", parts[4])
185            })?;
186            return Ok(StaticMachine::Front {
187                padding_budget_max,
188                window_min,
189                window_max,
190                num_states,
191            });
192        }
193
194        if s.contains("scrambler_server") {
195            let parts: Vec<&str> = s.split_whitespace().collect();
196            if parts.len() != 5 {
197                bail!("invalid scrambler server defense: {}", s);
198            }
199            let interval = parts[1].parse::<f64>().map_err(|_| {
200                anyhow::anyhow!(
201                    "invalid scrambler server defense parameter 'interval': {}",
202                    parts[1]
203                )
204            })?;
205            let min_count = parts[2].parse::<f64>().map_err(|_| {
206                anyhow::anyhow!(
207                    "invalid scrambler server defense parameter 'min_count': {}",
208                    parts[2]
209                )
210            })?;
211            let min_trail = parts[3].parse::<f64>().map_err(|_| {
212                anyhow::anyhow!(
213                    "invalid scrambler server defense parameter 'min_trail': {}",
214                    parts[3]
215                )
216            })?;
217            let max_trail = parts[4].parse::<f64>().map_err(|_| {
218                anyhow::anyhow!(
219                    "invalid scrambler server defense parameter 'max_trail': {}",
220                    parts[4]
221                )
222            })?;
223            return Ok(StaticMachine::ScramblerServer {
224                interval,
225                min_count,
226                min_trail,
227                max_trail,
228            });
229        }
230
231        match s {
232            "noop" => Ok(StaticMachine::NoOp),
233            "netflow" => Ok(StaticMachine::SimpleNetFlow),
234            "interspace_client" => Ok(StaticMachine::InterspaceClient),
235            "interspace_server" => Ok(StaticMachine::InterspaceServer),
236            "break_pad_client" => Ok(StaticMachine::BreakPadClient),
237            "break_pad_server" => Ok(StaticMachine::BreakPadServer),
238            "scrambler_client" => Ok(StaticMachine::ScramblerClient),
239            _ => bail!("invalid static machine: {}", s),
240        }
241    }
242}
243
244pub fn get_static_machine_strings() -> Vec<String> {
245    vec![
246        "noop".to_string(),
247        "netflow".to_string(),
248        "break_pad_client".to_string(),
249        "break_pad_server".to_string(),
250        "interspace_client".to_string(),
251        "interspace_server".to_string(),
252        "front padding_budget_max window_min window_max num_states".to_string(),
253        "tamaraw padding_rate stop_window".to_string(),
254        "regulator_client u c".to_string(),
255        "regulator_server r d t n b".to_string(),
256        "scrambler_client".to_string(),
257        "scrambler_server interval min_count min_trail max_trail".to_string(),
258    ]
259}
260
261/// Get one or more machines from a list of static machines.
262pub fn get_machine<R: RngCore>(s: &[StaticMachine], rng: &mut R) -> Vec<Machine> {
263    let mut machines = vec![];
264
265    for m in s {
266        match m {
267            StaticMachine::NoOp => machines.push(no_op_machine()),
268            StaticMachine::SimpleNetFlow => machines.push(netflow::simple_netflow()),
269            StaticMachine::RegulatorClient { u, c } => {
270                machines.extend(regulator::regulator_client(*u, *c))
271            }
272            StaticMachine::RegulatorServer { r, d, t, n, b } => {
273                machines.extend(regulator::regulator_server(*r, *d, *t, *n, *b))
274            }
275            StaticMachine::Tamaraw { p, stop_window } => {
276                machines.extend(tamaraw::tamaraw(*p, *stop_window))
277            }
278            StaticMachine::InterspaceClient => machines.extend(interspace::interspace_client(rng)),
279            StaticMachine::InterspaceServer => machines.extend(interspace::interspace_server(rng)),
280            StaticMachine::Front {
281                padding_budget_max,
282                window_min,
283                window_max,
284                num_states,
285            } => machines.extend(front::front(
286                *padding_budget_max,
287                *window_min,
288                *window_max,
289                *num_states,
290                rng,
291            )),
292            StaticMachine::BreakPadClient => machines.extend(break_pad::break_pad_client()),
293            StaticMachine::BreakPadServer => machines.extend(break_pad::break_pad_server()),
294            StaticMachine::ScramblerClient => machines.extend(scrambler::scrambler_client()),
295            StaticMachine::ScramblerServer {
296                interval,
297                min_count,
298                min_trail,
299                max_trail,
300            } => machines.extend(scrambler::scrambler_server(
301                *interval, *min_count, *min_trail, *max_trail,
302            )),
303        }
304    }
305
306    machines
307}
308
309fn no_op_machine() -> Machine {
310    let s0 = State::new(enum_map! {
311        _ => vec![],
312    });
313    Machine::new(0, 0.0, 0, 0.0, vec![s0]).unwrap()
314}
315
316// serialization tests for hardcoded machines handed out at different times
317#[cfg(test)]
318mod tests {
319    use maybenot::{
320        action::Action,
321        dist::{Dist, DistType},
322        event::Event,
323        state::Trans,
324    };
325
326    use crate::*;
327
328    #[test]
329    fn test_no_op() {
330        assert!(no_op_machine().validate().is_ok());
331        assert_eq!(no_op_machine().serialize(), "02eNpjYEAHjOgCAAA0AAI=")
332    }
333
334    #[test]
335    fn test_example_machine() {
336        let mut states = vec![];
337
338        let start_state = State::new(enum_map! {
339            Event::TunnelRecv => vec![Trans(1, 1.0)],
340            _ => vec![],
341        });
342        states.push(start_state);
343
344        let mut padding_state = State::new(enum_map! {
345            Event::PaddingSent => vec![Trans(0, 1.0)],
346            _ => vec![],
347        });
348        padding_state.action = Some(Action::SendPadding {
349            bypass: false,
350            replace: false,
351            timeout: Dist {
352                dist: DistType::Uniform {
353                    low: 0.0,
354                    high: 0.0,
355                },
356                start: 5_000_000.0,
357                max: 0.0,
358            },
359            limit: None,
360        });
361        states.push(padding_state);
362
363        let m = Machine::new(u64::MAX, 1.0, 0, 0.0, states).unwrap();
364        assert_eq!(
365            m.serialize(),
366            "02eNpti1EJACAQQzcjWEjMYCEjGsUCojiUg+Pex2CPbe0HxCz4JCVJoJvF7SEjt+qUtnY+gDUNIg=="
367        );
368    }
369
370    #[test]
371    fn test_ping_1s_loop_machine() {
372        let mut states = vec![];
373
374        let start_state = State::new(enum_map! {
375            Event::NormalSent => vec![Trans(1, 1.0)],
376            Event::NormalRecv => vec![Trans(1, 1.0)],
377            _ => vec![],
378        });
379        states.push(start_state);
380
381        let mut padding_state = State::new(enum_map! {
382            Event::PaddingSent => vec![Trans(1, 1.0)],
383            _ => vec![],
384        });
385        padding_state.action = Some(Action::SendPadding {
386            bypass: false,
387            replace: false,
388            timeout: Dist {
389                dist: DistType::Uniform {
390                    low: 0.0,
391                    high: 0.0,
392                },
393                start: 1_000_000.0,
394                max: 0.0,
395            },
396            limit: None,
397        });
398        states.push(padding_state);
399
400        let m = Machine::new(u64::MAX, 1.0, 0, 0.0, states).unwrap();
401        assert_eq!(
402            m.serialize(),
403            "02eNpty8sJACAMA9DEgVyhuFkPDuwC4o8KpfRBoQlkLoNnCL5yjiSg4h5zY0p7baEK2w33hw3i"
404        );
405    }
406}