1
1
Fork 0
mirror of https://tildegit.org/solderpunk/molly-brown synced 2024-04-28 06:45:03 +02:00

Initial implementation of leaky bucket rate limiting.

This commit is contained in:
Solderpunk 2023-03-16 20:27:45 +01:00
parent c4866d2965
commit 5016f40edb
3 changed files with 66 additions and 2 deletions

View File

@ -36,7 +36,7 @@ func isSubdir(subdir, superdir string) (bool, error) {
return false, nil
}
func handleGeminiRequest(conn net.Conn, sysConfig SysConfig, config UserConfig, accessLogEntries chan LogEntry, wg *sync.WaitGroup) {
func handleGeminiRequest(conn net.Conn, sysConfig SysConfig, config UserConfig, accessLogEntries chan LogEntry, rl *RateLimiter, wg *sync.WaitGroup) {
defer conn.Close()
defer wg.Done()
var tlsConn (*tls.Conn) = conn.(*tls.Conn)
@ -49,6 +49,15 @@ func handleGeminiRequest(conn net.Conn, sysConfig SysConfig, config UserConfig,
defer func() { accessLogEntries <- logEntry }()
}
// Enforce rate limiting
noPort := logEntry.RemoteAddr.String()
noPort = noPort[0:strings.LastIndex(noPort, ":")]
if !rl.Allowed(noPort) {
conn.Write([]byte("44 10 second cool down, please!\r\n"))
logEntry.Status = 44
return
}
// Read request
URL, err := readRequest(conn, &logEntry)
if err != nil {

View File

@ -159,11 +159,12 @@ func launch(sysConfig SysConfig, userConfig UserConfig, privInfo userInfo) int {
// Infinite serve loop (SIGTERM breaks out)
running := true
var wg sync.WaitGroup
rl := newRateLimiter(100, 5)
for running {
conn, err := listener.Accept()
if err == nil {
wg.Add(1)
go handleGeminiRequest(conn, sysConfig, userConfig, accessLogEntries, &wg)
go handleGeminiRequest(conn, sysConfig, userConfig, accessLogEntries, &rl, &wg)
} else {
select {
case <-shutdown:

54
ratelim.go Normal file
View File

@ -0,0 +1,54 @@
package main
import (
"fmt"
"sync"
"time"
)
type RateLimiter struct {
mu sync.Mutex
bucket map[string]int
capacity int
rate int
}
func newRateLimiter(capacity int, rate int) RateLimiter {
var rl = new(RateLimiter)
rl.bucket = make(map[string]int)
rl.capacity = capacity
rl.rate = rate
// Leak periodically
go func () {
for(true) {
fmt.Println(rl.bucket)
rl.mu.Lock()
for addr, drips := range rl.bucket {
if drips <= rate {
delete(rl.bucket, addr)
} else {
rl.bucket[addr] = drips - rl.rate
}
}
rl.mu.Unlock()
time.Sleep(time.Second)
}
}()
return *rl
}
func (rl *RateLimiter) Allowed(addr string) bool {
rl.mu.Lock()
defer rl.mu.Unlock()
drips, present := rl.bucket[addr]
if !present {
rl.bucket[addr] = 1
return true
}
if drips == rl.capacity {
return false
}
rl.bucket[addr] = drips + 1
return true
}