1use std::path::PathBuf;
6
7use serde::{Deserialize, Serialize, de::Deserializer};
8use serde_with::serde_as;
9
10pub const DEFAULT_SKETCH_CAPACITY: usize = 50_000;
15pub const DEFAULT_SKETCH_PROBABILITY: f64 = 0.999;
16pub const DEFAULT_SKETCH_TOLERANCE: f64 = 0.2;
17use rand::distributions::Distribution;
18
19const TRAFFIC_SINK_TIMEOUT_SEC: u64 = 300;
20
21#[derive(Clone, Debug, Deserialize, Serialize, Default)]
71#[serde(rename_all = "kebab-case")]
72pub enum ClientIdSource {
73 #[default]
74 SocketAddr,
75 XForwardedFor(usize),
76}
77
78#[derive(Clone, Debug, Deserialize, Serialize)]
79pub struct TrafficControlReconfigParams {
80 pub error_threshold: Option<u64>,
81 pub spam_threshold: Option<u64>,
82 pub dry_run: Option<bool>,
83}
84
85#[derive(Clone, Debug, Deserialize, Serialize)]
86pub struct Weight(f32);
87
88impl Weight {
89 pub fn new(value: f32) -> Result<Self, &'static str> {
90 if (0.0..=1.0).contains(&value) {
91 Ok(Self(value))
92 } else {
93 Err("Weight must be between 0.0 and 1.0")
94 }
95 }
96
97 pub fn one() -> Self {
98 Self(1.0)
99 }
100
101 pub fn zero() -> Self {
102 Self(0.0)
103 }
104
105 pub fn value(&self) -> f32 {
106 self.0
107 }
108
109 pub fn is_sampled(&self) -> bool {
110 let mut rng = rand::thread_rng();
111 let sample = rand::distributions::Uniform::new(0.0, 1.0).sample(&mut rng);
114 self.accepts(sample)
115 }
116
117 fn accepts(&self, sample: f32) -> bool {
118 sample < self.value()
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::Weight;
125
126 #[test]
127 fn zero_weight_rejects_the_lowest_sample() {
128 assert!(!Weight::zero().accepts(0.0));
129 }
130
131 #[test]
132 fn full_weight_accepts_the_highest_sample() {
133 assert!(Weight::one().accepts(1.0 - f32::EPSILON));
134 }
135}
136
137fn validate_sample_rate<'de, D>(deserializer: D) -> Result<Weight, D::Error>
138where
139 D: Deserializer<'de>,
140{
141 let value = f32::deserialize(deserializer)?;
142 Weight::new(value)
143 .map_err(|_| serde::de::Error::custom("spam-sample-rate must be between 0.0 and 1.0"))
144}
145
146impl PartialEq for Weight {
147 fn eq(&self, other: &Self) -> bool {
148 self.value() == other.value()
149 }
150}
151
152#[serde_as]
153#[derive(Clone, Debug, Deserialize, Serialize)]
154#[serde(rename_all = "kebab-case")]
155pub struct RemoteFirewallConfig {
156 pub remote_fw_url: String,
157 pub destination_port: u16,
158 #[serde(default)]
159 pub delegate_spam_blocking: bool,
160 #[serde(default)]
161 pub delegate_error_blocking: bool,
162 #[serde(default = "default_drain_path")]
163 pub drain_path: PathBuf,
164 #[serde(default = "default_drain_timeout")]
167 pub drain_timeout_secs: u64,
168}
169
170fn default_drain_path() -> PathBuf {
171 PathBuf::from("/tmp/drain")
172}
173
174fn default_drain_timeout() -> u64 {
175 TRAFFIC_SINK_TIMEOUT_SEC
176}
177
178#[serde_as]
179#[derive(Clone, Debug, Deserialize, Serialize)]
180#[serde(rename_all = "kebab-case")]
181pub struct FreqThresholdConfig {
182 #[serde(default = "default_client_threshold")]
183 pub client_threshold: u64,
184 #[serde(default = "default_proxied_client_threshold")]
185 pub proxied_client_threshold: u64,
186 #[serde(default = "default_window_size_secs")]
187 pub window_size_secs: u64,
188 #[serde(default = "default_update_interval_secs")]
189 pub update_interval_secs: u64,
190 #[serde(default = "default_sketch_capacity")]
191 pub sketch_capacity: usize,
192 #[serde(default = "default_sketch_probability")]
193 pub sketch_probability: f64,
194 #[serde(default = "default_sketch_tolerance")]
195 pub sketch_tolerance: f64,
196}
197
198impl Default for FreqThresholdConfig {
199 fn default() -> Self {
200 Self {
201 client_threshold: default_client_threshold(),
202 proxied_client_threshold: default_proxied_client_threshold(),
203 window_size_secs: default_window_size_secs(),
204 update_interval_secs: default_update_interval_secs(),
205 sketch_capacity: default_sketch_capacity(),
206 sketch_probability: default_sketch_probability(),
207 sketch_tolerance: default_sketch_tolerance(),
208 }
209 }
210}
211
212fn default_client_threshold() -> u64 {
213 1_000_000
220}
221
222fn default_proxied_client_threshold() -> u64 {
223 10
224}
225
226fn default_window_size_secs() -> u64 {
227 30
228}
229
230fn default_update_interval_secs() -> u64 {
231 5
232}
233
234fn default_sketch_capacity() -> usize {
235 DEFAULT_SKETCH_CAPACITY
236}
237
238fn default_sketch_probability() -> f64 {
239 DEFAULT_SKETCH_PROBABILITY
240}
241
242fn default_sketch_tolerance() -> f64 {
243 DEFAULT_SKETCH_TOLERANCE
244}
245
246#[derive(Clone, Serialize, Deserialize, Debug, Default)]
249pub enum PolicyType {
250 #[default]
252 NoOp,
253
254 #[serde(rename = "freq-threshold", alias = "FreqThreshold")]
258 FreqThreshold(FreqThresholdConfig),
259
260 TestNConnIP(u64),
265 TestPanicOnInvocation,
269}
270
271#[serde_as]
272#[derive(Clone, Debug, Deserialize, Serialize)]
273#[serde(rename_all = "kebab-case")]
274pub struct PolicyConfig {
275 #[serde(default = "default_client_id_source")]
276 pub client_id_source: ClientIdSource,
277 #[serde(default = "default_connection_blocklist_ttl_sec")]
278 pub connection_blocklist_ttl_sec: u64,
279 #[serde(default)]
280 pub proxy_blocklist_ttl_sec: u64,
281 #[serde(default)]
282 pub spam_policy_type: PolicyType,
283 #[serde(default)]
284 pub error_policy_type: PolicyType,
285 #[serde(default = "default_channel_capacity")]
286 pub channel_capacity: usize,
287 #[serde(
288 default = "default_spam_sample_rate",
289 deserialize_with = "validate_sample_rate"
290 )]
291 pub spam_sample_rate: Weight,
297 #[serde(default = "default_dry_run")]
298 pub dry_run: bool,
299 #[serde(default)]
303 pub allow_list: Option<Vec<String>>,
304}
305
306impl Default for PolicyConfig {
307 fn default() -> Self {
308 Self {
309 client_id_source: default_client_id_source(),
310 connection_blocklist_ttl_sec: 0,
311 proxy_blocklist_ttl_sec: 0,
312 spam_policy_type: PolicyType::NoOp,
313 error_policy_type: PolicyType::NoOp,
314 channel_capacity: 100,
315 spam_sample_rate: default_spam_sample_rate(),
316 dry_run: default_dry_run(),
317 allow_list: None,
318 }
319 }
320}
321
322impl PolicyConfig {
323 pub fn default_dos_protection_policy() -> Self {
324 Self {
325 client_id_source: ClientIdSource::SocketAddr,
326 spam_policy_type: PolicyType::FreqThreshold(FreqThresholdConfig {
327 client_threshold: 1000,
328 window_size_secs: 5,
329 update_interval_secs: 1,
330 ..FreqThresholdConfig::default()
331 }),
332 error_policy_type: PolicyType::FreqThreshold(FreqThresholdConfig {
333 client_threshold: 50,
334 window_size_secs: 5,
335 update_interval_secs: 1,
336 ..FreqThresholdConfig::default()
337 }),
338 channel_capacity: 6000,
339 spam_sample_rate: Weight::new(1.0).unwrap(),
340 dry_run: true,
341 ..Self::default()
342 }
343 }
344}
345
346pub fn default_client_id_source() -> ClientIdSource {
347 ClientIdSource::SocketAddr
348}
349
350pub fn default_connection_blocklist_ttl_sec() -> u64 {
351 60
352}
353pub fn default_channel_capacity() -> usize {
354 100
355}
356
357pub fn default_dry_run() -> bool {
358 true
359}
360
361pub fn default_spam_sample_rate() -> Weight {
362 Weight::new(0.2).unwrap()
363}