Skip to main content

shadow_network_sim/
censorship.rs

1//! Censorship simulation — model how censors block, throttle, and detect traffic.
2
3use serde::{Serialize, Deserialize};
4
5/// Types of censorship mechanisms.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7pub enum CensorshipMethod {
8    /// Block by IP address.
9    IpBlocking,
10    /// Block by destination port.
11    PortBlocking,
12    /// Deep packet inspection for protocol signatures.
13    Dpi,
14    /// Throttle bandwidth below usability.
15    Throttling,
16    /// DNS poisoning / hijacking.
17    DnsPoisoning,
18    /// TCP RST injection.
19    TcpReset,
20    /// Active probing to detect circumvention tools.
21    ActiveProbing,
22}
23
24/// A censorship rule that triggers on specific conditions.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct BlockingRule {
27    pub method: CensorshipMethod,
28    /// Pattern to match (IP prefix, port number, protocol signature, etc.).
29    pub pattern: String,
30    /// Human-readable description.
31    pub description: String,
32    /// Whether this rule is currently active.
33    pub active: bool,
34}
35
36impl BlockingRule {
37    pub fn ip_block(ip_prefix: &str) -> Self {
38        Self {
39            method: CensorshipMethod::IpBlocking,
40            pattern: ip_prefix.to_string(),
41            description: format!("Block IP prefix {}", ip_prefix),
42            active: true,
43        }
44    }
45
46    pub fn port_block(port: u16) -> Self {
47        Self {
48            method: CensorshipMethod::PortBlocking,
49            pattern: port.to_string(),
50            description: format!("Block port {}", port),
51            active: true,
52        }
53    }
54
55    pub fn dpi_rule(signature: &str) -> Self {
56        Self {
57            method: CensorshipMethod::Dpi,
58            pattern: signature.to_string(),
59            description: format!("DPI detect: {}", signature),
60            active: true,
61        }
62    }
63
64    pub fn throttle_rule(pattern: &str) -> Self {
65        Self {
66            method: CensorshipMethod::Throttling,
67            pattern: pattern.to_string(),
68            description: format!("Throttle: {}", pattern),
69            active: true,
70        }
71    }
72}
73
74/// Simulated network packet for censorship testing.
75#[derive(Debug, Clone)]
76pub struct SimulatedPacket {
77    pub source_ip: String,
78    pub dest_ip: String,
79    pub source_port: u16,
80    pub dest_port: u16,
81    pub payload: Vec<u8>,
82    pub protocol: String,
83}
84
85/// Result of censorship check.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum CensorResult {
88    /// Packet passed through unmodified.
89    Allowed,
90    /// Packet was blocked.
91    Blocked(String),
92    /// Packet was throttled (reduced bandwidth).
93    Throttled(String),
94    /// Connection was reset via TCP RST.
95    Reset(String),
96}
97
98/// Censorship simulator with configurable rule sets.
99pub struct CensorshipSimulator {
100    rules: Vec<BlockingRule>,
101    /// Statistics.
102    packets_seen: u64,
103    packets_blocked: u64,
104    packets_throttled: u64,
105}
106
107impl CensorshipSimulator {
108    pub fn new() -> Self {
109        Self {
110            rules: Vec::new(),
111            packets_seen: 0,
112            packets_blocked: 0,
113            packets_throttled: 0,
114        }
115    }
116
117    /// Create a simulator with common censorship rules.
118    pub fn with_standard_rules() -> Self {
119        let mut sim = Self::new();
120        // Block common circumvention ports
121        sim.add_rule(BlockingRule::port_block(9001)); // Tor default
122        sim.add_rule(BlockingRule::port_block(443));  // HTTPS (extreme)
123        // DPI for known protocols
124        sim.add_rule(BlockingRule::dpi_rule("TLS_OBFS"));
125        sim.add_rule(BlockingRule::dpi_rule("SHADOW"));
126        // Throttle VPN-like traffic
127        sim.add_rule(BlockingRule::throttle_rule("VPN_PATTERN"));
128        sim
129    }
130
131    /// Add a blocking rule.
132    pub fn add_rule(&mut self, rule: BlockingRule) {
133        self.rules.push(rule);
134    }
135
136    /// Remove all rules.
137    pub fn clear_rules(&mut self) {
138        self.rules.clear();
139    }
140
141    /// Check a packet against all active rules.
142    pub fn check_packet(&mut self, packet: &SimulatedPacket) -> CensorResult {
143        self.packets_seen += 1;
144
145        for rule in &self.rules {
146            if !rule.active {
147                continue;
148            }
149
150            match rule.method {
151                CensorshipMethod::IpBlocking => {
152                    if packet.dest_ip.starts_with(&rule.pattern)
153                        || packet.source_ip.starts_with(&rule.pattern)
154                    {
155                        self.packets_blocked += 1;
156                        return CensorResult::Blocked(rule.description.clone());
157                    }
158                }
159                CensorshipMethod::PortBlocking => {
160                    if let Ok(port) = rule.pattern.parse::<u16>() {
161                        if packet.dest_port == port || packet.source_port == port {
162                            self.packets_blocked += 1;
163                            return CensorResult::Blocked(rule.description.clone());
164                        }
165                    }
166                }
167                CensorshipMethod::Dpi => {
168                    // Check payload for signature bytes
169                    let sig = rule.pattern.as_bytes();
170                    if packet.payload.windows(sig.len()).any(|w| w == sig) {
171                        self.packets_blocked += 1;
172                        return CensorResult::Blocked(rule.description.clone());
173                    }
174                    // Also check protocol field
175                    if packet.protocol.contains(&rule.pattern) {
176                        self.packets_blocked += 1;
177                        return CensorResult::Blocked(rule.description.clone());
178                    }
179                }
180                CensorshipMethod::Throttling => {
181                    if packet.protocol.contains(&rule.pattern) {
182                        self.packets_throttled += 1;
183                        return CensorResult::Throttled(rule.description.clone());
184                    }
185                }
186                CensorshipMethod::TcpReset => {
187                    if packet.dest_ip.starts_with(&rule.pattern) {
188                        self.packets_blocked += 1;
189                        return CensorResult::Reset(rule.description.clone());
190                    }
191                }
192                _ => {}
193            }
194        }
195
196        CensorResult::Allowed
197    }
198
199    /// Batch test: returns (allowed, blocked, throttled) counts.
200    pub fn batch_test(&mut self, packets: &[SimulatedPacket]) -> (usize, usize, usize) {
201        let mut allowed = 0;
202        let mut blocked = 0;
203        let mut throttled = 0;
204
205        for pkt in packets {
206            match self.check_packet(pkt) {
207                CensorResult::Allowed => allowed += 1,
208                CensorResult::Blocked(_) => blocked += 1,
209                CensorResult::Throttled(_) => throttled += 1,
210                CensorResult::Reset(_) => blocked += 1,
211            }
212        }
213
214        (allowed, blocked, throttled)
215    }
216
217    /// Get statistics.
218    pub fn stats(&self) -> CensorStats {
219        CensorStats {
220            packets_seen: self.packets_seen,
221            packets_blocked: self.packets_blocked,
222            packets_throttled: self.packets_throttled,
223            rules_active: self.rules.iter().filter(|r| r.active).count(),
224        }
225    }
226
227    /// Number of rules.
228    pub fn rule_count(&self) -> usize {
229        self.rules.len()
230    }
231}
232
233impl Default for CensorshipSimulator {
234    fn default() -> Self {
235        Self::new()
236    }
237}
238
239/// Censorship statistics.
240#[derive(Debug, Clone)]
241pub struct CensorStats {
242    pub packets_seen: u64,
243    pub packets_blocked: u64,
244    pub packets_throttled: u64,
245    pub rules_active: usize,
246}
247
248impl CensorStats {
249    pub fn block_rate(&self) -> f64 {
250        if self.packets_seen == 0 {
251            0.0
252        } else {
253            self.packets_blocked as f64 / self.packets_seen as f64
254        }
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    fn make_packet(dest_ip: &str, dest_port: u16, payload: &[u8], protocol: &str) -> SimulatedPacket {
263        SimulatedPacket {
264            source_ip: "10.0.0.1".into(),
265            dest_ip: dest_ip.into(),
266            source_port: 12345,
267            dest_port,
268            payload: payload.to_vec(),
269            protocol: protocol.into(),
270        }
271    }
272
273    #[test]
274    fn test_ip_blocking() {
275        let mut sim = CensorshipSimulator::new();
276        sim.add_rule(BlockingRule::ip_block("192.168.1"));
277
278        let blocked = make_packet("192.168.1.5", 80, b"hello", "TCP");
279        let allowed = make_packet("10.0.0.5", 80, b"hello", "TCP");
280
281        assert!(matches!(sim.check_packet(&blocked), CensorResult::Blocked(_)));
282        assert_eq!(sim.check_packet(&allowed), CensorResult::Allowed);
283    }
284
285    #[test]
286    fn test_port_blocking() {
287        let mut sim = CensorshipSimulator::new();
288        sim.add_rule(BlockingRule::port_block(9001));
289
290        let blocked = make_packet("10.0.0.5", 9001, b"data", "TCP");
291        let allowed = make_packet("10.0.0.5", 80, b"data", "TCP");
292
293        assert!(matches!(sim.check_packet(&blocked), CensorResult::Blocked(_)));
294        assert_eq!(sim.check_packet(&allowed), CensorResult::Allowed);
295    }
296
297    #[test]
298    fn test_dpi_detection() {
299        let mut sim = CensorshipSimulator::new();
300        sim.add_rule(BlockingRule::dpi_rule("SHADOW"));
301
302        // Payload contains the signature
303        let detected = make_packet("10.0.0.5", 443, b"xyzSHADOWabc", "TLS");
304        let clean = make_packet("10.0.0.5", 443, b"normal traffic", "TLS");
305
306        assert!(matches!(sim.check_packet(&detected), CensorResult::Blocked(_)));
307        assert_eq!(sim.check_packet(&clean), CensorResult::Allowed);
308    }
309
310    #[test]
311    fn test_throttling() {
312        let mut sim = CensorshipSimulator::new();
313        sim.add_rule(BlockingRule::throttle_rule("VPN"));
314
315        let vpn = make_packet("10.0.0.5", 1194, b"data", "VPN_TUNNEL");
316        let normal = make_packet("10.0.0.5", 80, b"data", "HTTP");
317
318        assert!(matches!(sim.check_packet(&vpn), CensorResult::Throttled(_)));
319        assert_eq!(sim.check_packet(&normal), CensorResult::Allowed);
320    }
321
322    #[test]
323    fn test_batch_statistics() {
324        let mut sim = CensorshipSimulator::with_standard_rules();
325
326        let packets = vec![
327            make_packet("10.0.0.5", 80, b"normal", "HTTP"),
328            make_packet("10.0.0.5", 9001, b"tor data", "TCP"),
329            make_packet("10.0.0.5", 8080, b"safe", "HTTP"),
330        ];
331
332        let (allowed, blocked, _throttled) = sim.batch_test(&packets);
333        assert_eq!(allowed, 2);
334        assert_eq!(blocked, 1);
335    }
336}