Initial commit: DDNS service with NIC V2 protocol support
Features: - Token-based subdomain claiming - NIC V2 (DynDNS2) protocol implementation - Technitium DNS integration - Rate limiting (10 req/min IP, 1 req/min token) - Web UI for space claiming - Docker/Docker Compose support - Compatible with UniFi, pfSense, EdgeRouter Module: git.dws.rip/DWS/dyn
This commit is contained in:
112
internal/middleware/ratelimit.go
Normal file
112
internal/middleware/ratelimit.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type RateLimiter struct {
|
||||
ipLimits map[string]*RateLimitEntry
|
||||
tokenLimits map[string]*RateLimitEntry
|
||||
mu sync.RWMutex
|
||||
limitPerIP int
|
||||
limitPerToken int
|
||||
}
|
||||
|
||||
type RateLimitEntry struct {
|
||||
Count int
|
||||
ResetTime time.Time
|
||||
}
|
||||
|
||||
func NewRateLimiter(perIP, perToken int) *RateLimiter {
|
||||
return &RateLimiter{
|
||||
ipLimits: make(map[string]*RateLimitEntry),
|
||||
tokenLimits: make(map[string]*RateLimitEntry),
|
||||
limitPerIP: perIP,
|
||||
limitPerToken: perToken,
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) RateLimitByIP() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ip := c.ClientIP()
|
||||
|
||||
rl.mu.Lock()
|
||||
entry, exists := rl.ipLimits[ip]
|
||||
now := time.Now()
|
||||
|
||||
if !exists || now.After(entry.ResetTime) {
|
||||
rl.ipLimits[ip] = &RateLimitEntry{
|
||||
Count: 1,
|
||||
ResetTime: now.Add(time.Minute),
|
||||
}
|
||||
rl.mu.Unlock()
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
if entry.Count >= rl.limitPerIP {
|
||||
rl.mu.Unlock()
|
||||
c.String(http.StatusTooManyRequests, "rate limit exceeded")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
entry.Count++
|
||||
rl.mu.Unlock()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) RateLimitByToken() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := extractToken(c)
|
||||
if token == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
rl.mu.Lock()
|
||||
entry, exists := rl.tokenLimits[token]
|
||||
now := time.Now()
|
||||
|
||||
if !exists || now.After(entry.ResetTime) {
|
||||
rl.tokenLimits[token] = &RateLimitEntry{
|
||||
Count: 1,
|
||||
ResetTime: now.Add(time.Minute),
|
||||
}
|
||||
rl.mu.Unlock()
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
if entry.Count >= rl.limitPerToken {
|
||||
rl.mu.Unlock()
|
||||
c.String(http.StatusTooManyRequests, "rate limit exceeded")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
entry.Count++
|
||||
rl.mu.Unlock()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func extractToken(c *gin.Context) string {
|
||||
auth := c.GetHeader("Authorization")
|
||||
if auth == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Basic" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return parts[1]
|
||||
}
|
||||
Reference in New Issue
Block a user