Skip to main content

turmoil_net/
rule.rs

1//! Packet rules — the extension point for latency, drops, partitions,
2//! and other fault injection.
3//!
4//! The fabric consults every installed rule, in install order, for
5//! each non-loopback packet leaving a host. The first rule to return
6//! a non-[`Verdict::Pass`] verdict wins; if every rule passes, the
7//! packet is delivered immediately.
8//!
9//! Loopback is inline and skips rules — a single-host test doesn't
10//! need fault injection on 127.0.0.1 traffic, and bypassing keeps the
11//! fast path fast.
12//!
13//! # Installing
14//!
15//! ```ignore
16//! // Free fn (from any task inside the Net): uninstalls on drop.
17//! let guard = turmoil_net::rule(Latency::fixed(Duration::from_millis(10)));
18//! // Or permanently at Net construction:
19//! let net = Net::new();
20//! net.install_permanent(Latency::fixed(/* .. */));
21//! ```
22
23use std::{marker::PhantomData, time::Duration};
24
25use crate::{kernel::Packet, uninstall_rule};
26
27/// What should happen to a packet.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum Verdict {
30    /// Defer to the next rule. If every rule passes, the packet is
31    /// delivered with zero delay.
32    Pass,
33    /// Deliver the packet, optionally after `delay`. `Duration::ZERO`
34    /// means "immediately" — functionally equivalent to the default
35    /// no-rules behavior, but short-circuits any later rules.
36    Deliver(Duration),
37    /// Drop the packet silently.
38    Drop,
39}
40
41/// Decides the fate of each packet the fabric sees.
42///
43/// Rules have exclusive `&mut` during evaluation, so they can hold
44/// counters, RNG state, or other per-rule bookkeeping without
45/// synchronization. Evaluation is deterministic: rules run in install
46/// order, and for each packet the first non-[`Verdict::Pass`] wins.
47pub trait Rule: 'static {
48    fn on_packet(&mut self, pkt: &Packet) -> Verdict;
49}
50
51/// Handle returned by every installer. The `RuleId` is stable for the
52/// life of the rule — uninstalling twice is a no-op.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub struct RuleId(pub(crate) u64);
55
56/// A rule that delays every packet by a fixed duration.
57///
58/// Apply selectively by src/dst if you want one-way latency — the
59/// matcher closure is called for each packet and receives the
60/// endpoints.
61#[derive(Debug, Clone, Copy)]
62pub struct Latency {
63    delay: Duration,
64}
65
66impl Latency {
67    /// Delay every packet by `delay`. Returning a rule that matches
68    /// only specific endpoints is left to the caller — wrap this in
69    /// your own [`Rule`] impl and call `Latency::on_packet` from it,
70    /// or pattern-match src/dst directly in a closure rule.
71    pub fn fixed(delay: Duration) -> Self {
72        Self { delay }
73    }
74}
75
76impl Rule for Latency {
77    fn on_packet(&mut self, _pkt: &Packet) -> Verdict {
78        Verdict::Deliver(self.delay)
79    }
80}
81
82/// Adapter that lifts a `FnMut(&Packet) -> Verdict` into a [`Rule`].
83/// Lets tests write ad-hoc rules without a full type declaration.
84impl<F> Rule for F
85where
86    F: FnMut(&Packet) -> Verdict + 'static,
87{
88    fn on_packet(&mut self, pkt: &Packet) -> Verdict {
89        self(pkt)
90    }
91}
92
93/// RAII handle for a rule installed via [`rule`](crate::rule) or
94/// [`EnterGuard::rule`](crate::EnterGuard::rule).
95/// Drop uninstalls the rule; [`RuleGuard::forget`] leaks it instead,
96/// leaving the rule installed for the rest of the simulation.
97///
98/// The guard is `!Send`: it's tied to the thread that owns the `Net`,
99/// and the thread-local uninstall on drop would be unsound across
100/// threads.
101#[must_use = "dropping the guard uninstalls the rule"]
102pub struct RuleGuard {
103    id: RuleId,
104    _not_send: PhantomData<*const ()>,
105}
106
107impl RuleGuard {
108    pub fn new(id: RuleId) -> Self {
109        Self {
110            id,
111            _not_send: PhantomData,
112        }
113    }
114
115    pub fn id(&self) -> RuleId {
116        self.id
117    }
118
119    /// Leak the guard — the rule stays installed until the `Net` is
120    /// dropped. Useful when an early-phase guard should survive the
121    /// call that produced it.
122    pub fn forget(self) {
123        std::mem::forget(self);
124    }
125}
126
127impl Drop for RuleGuard {
128    fn drop(&mut self) {
129        uninstall_rule(self.id);
130    }
131}