commit ddff59a20ac86ddc300901c287fc834ce3f80eb7
parent c951f0abb2790e4a3d59cbdc277fcfa24e0323f4
Author: William Casarin <jb55@jb55.com>
Date: Mon, 8 Jul 2024 14:12:38 -0700
ratelimit: switch to token-based rate limiting
allows for N actions per minute
Changelog-Changed: Switched to token-based ratelimiting
Closes: https://github.com/damus-io/noteguard/issues/2
Diffstat:
4 files changed, 57 insertions(+), 27 deletions(-)
diff --git a/README.md b/README.md
@@ -18,7 +18,7 @@ The `pipeline` config specifies the order in which filters are run. When the fir
pipeline = ["ratelimit"]
[filters.ratelimit]
-delay_seconds = 1
+notes_per_minute = 8
whitelist = ["127.0.0.1"]
```
@@ -34,7 +34,7 @@ The ratelimit filter limits the rate at which notes are written to the relay per
Settings:
-- `delay_seconds`: the delay in seconds between accepted notes. 1 means only one note can be written per second. 2 means only 1 note can be written every 2 seconds, etc.
+- `notes_per_minute`: the number of notes per minute which are allowed to be written per ip.
- `whitelist`: a list of IP4 or IP6 addresses that are allowed to bypass the ratelimit.
diff --git a/noteguard.toml b/noteguard.toml
@@ -2,5 +2,5 @@
pipeline = ["ratelimit"]
[filters.ratelimit]
-delay_seconds = 1
-whitelist = ["127.0.0.1"]
+posts_per_minute = 10
+whitelist = ["127.0.0.10"]
diff --git a/src/filters/rate_limit.rs b/src/filters/rate_limit.rs
@@ -3,20 +3,25 @@ use serde::Deserialize;
use std::collections::HashMap;
use std::time::{Duration, Instant};
-pub struct RateInfo {
- pub last_note: Instant,
+pub struct Tokens {
+ pub tokens: i32,
+ pub last_post: Instant,
}
#[derive(Deserialize, Default)]
pub struct RateLimit {
- pub delay_seconds: u64,
+ pub posts_per_minute: i32,
pub whitelist: Option<Vec<String>>,
#[serde(skip)]
- pub sources: HashMap<String, RateInfo>,
+ pub sources: HashMap<String, Tokens>,
}
impl NoteFilter for RateLimit {
+ fn name(&self) -> &'static str {
+ "ratelimit"
+ }
+
fn filter_note(&mut self, msg: &InputMessage) -> OutputMessage {
if let Some(whitelist) = &self.whitelist {
if whitelist.contains(&msg.source_info) {
@@ -24,31 +29,47 @@ impl NoteFilter for RateLimit {
}
}
- if self.sources.contains_key(&msg.source_info) {
- let now = Instant::now();
- let entry = self.sources.get_mut(&msg.source_info).expect("impossiburu");
- if now - entry.last_note < Duration::from_secs(self.delay_seconds) {
- return OutputMessage::new(
- msg.event.id.clone(),
- Action::Reject,
- Some("rate-limited: you are noting too fast".to_string()),
- );
- } else {
- entry.last_note = Instant::now();
- return OutputMessage::new(msg.event.id.clone(), Action::Accept, None);
- }
- } else {
+ if !self.sources.contains_key(&msg.source_info) {
self.sources.insert(
msg.source_info.to_owned(),
- RateInfo {
- last_note: Instant::now(),
+ Tokens {
+ last_post: Instant::now(),
+ tokens: self.posts_per_minute,
},
);
return OutputMessage::new(msg.event.id.clone(), Action::Accept, None);
}
- }
- fn name(&self) -> &'static str {
- "ratelimit"
+ let entry = self.sources.get_mut(&msg.source_info).expect("impossiburu");
+ let now = Instant::now();
+ let mut diff = now - entry.last_post;
+
+ let min = Duration::from_secs(60);
+ if diff > min {
+ diff = min;
+ }
+
+ let percent = (diff.as_secs() as f32) / 60.0;
+ let new_tokens = (percent * self.posts_per_minute as f32).floor() as i32;
+ entry.tokens += new_tokens - 1;
+
+ if entry.tokens <= 0 {
+ entry.tokens = 0;
+ }
+
+ if entry.tokens >= self.posts_per_minute {
+ entry.tokens = self.posts_per_minute - 1;
+ }
+
+ if entry.tokens == 0 {
+ return OutputMessage::new(
+ msg.event.id.clone(),
+ Action::Reject,
+ Some("rate-limited: you are noting too much".to_string()),
+ );
+ }
+
+ entry.last_post = now;
+ OutputMessage::new(msg.event.id.clone(), Action::Accept, None)
}
}
diff --git a/test/test-delayed b/test/test-delayed
@@ -0,0 +1,9 @@
+#!/usr/bin/env sh
+
+while true
+do
+ echo '{"type": "new","receivedAt":12345,"sourceType":"IP4","sourceInfo": "127.0.0.2","event":{"id": "68421a122cef086512b2c5bd29ca6285ced8bd8e302e347e3c5d90466c860a76","pubkey": "16c21558762108afc34e4ff19e4ed51d9a48f79e0c34531efc423d21ab435e93","created_at": 1720408658,"kind": 1,"tags": [],"content": "hi","sig": "7b76471744ded2b720ca832cdc89e670f6093ce38aeef55a5c6a4e077883d7d80dda1e9051032fb1faa1c3c212c517e93ee42b3ceac8e8e9b04bad46a361de90"}}'
+
+ sleep 0.1
+done
+