feat: HTTP FLOOD Speed Limit

add shared memory
This commit is contained in:
Roi Feng
2025-04-09 20:52:40 -04:00
parent 4dad82eb40
commit b49c3573bb
14 changed files with 63 additions and 20 deletions

View File

@ -25,7 +25,7 @@ type HCaptchaResponse struct {
ErrorCodes []string `json:"error-codes"` ErrorCodes []string `json:"error-codes"`
} }
func Captcha(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision) { func Captcha(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision, sharedMem *dataType.SharedMemory) {
if !reqData.Captcha { if !reqData.Captcha {
decision.Set(action.Continue) decision.Set(action.Continue)
return return

View File

@ -0,0 +1,35 @@
package check
import (
"log"
"server_torii/internal/action"
"server_torii/internal/config"
"server_torii/internal/dataType"
)
func HTTPFlood(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision, sharedMem *dataType.SharedMemory) {
ipKey := reqData.RemoteIP
sharedMem.HTTPFloodSpeedLimitCounter.Add(ipKey, 1)
uriKey := reqData.RemoteIP + "|" + reqData.Uri
sharedMem.HTTPFloodSameURILimitCounter.Add(uriKey, 1)
for window, limit := range ruleSet.HTTPFloodRule.HTTPFloodSpeedLimit {
if sharedMem.HTTPFloodSpeedLimitCounter.Query(ipKey, window) > limit {
log.Printf("HTTPFlood rate limit exceeded: IP %s, window %d, limit %d", ipKey, window, limit)
//decision.SetResponse(action.Done, []byte("403"), nil)
decision.Set(action.Continue)
return
}
}
for window, limit := range ruleSet.HTTPFloodRule.HTTPFloodSameURILimit {
if sharedMem.HTTPFloodSameURILimitCounter.Query(uriKey, window) > limit {
log.Printf("HTTPFlood URI rate limit exceeded: IP %s, URI %s, window %d, limit %d", ipKey, reqData.Uri, window, limit)
//decision.SetResponse(action.Done, []byte("403"), nil)
decision.Set(action.Continue)
return
}
}
decision.Set(action.Continue)
}

View File

@ -7,7 +7,7 @@ import (
"server_torii/internal/dataType" "server_torii/internal/dataType"
) )
func IPAllowList(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision) { func IPAllowList(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision, sharedMem *dataType.SharedMemory) {
remoteIP := reqData.RemoteIP remoteIP := reqData.RemoteIP
trie := ruleSet.IPAllowTrie trie := ruleSet.IPAllowTrie

View File

@ -7,7 +7,7 @@ import (
"server_torii/internal/dataType" "server_torii/internal/dataType"
) )
func IPBlockList(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision) { func IPBlockList(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision, sharedMem *dataType.SharedMemory) {
remoteIP := reqData.RemoteIP remoteIP := reqData.RemoteIP
trie := ruleSet.IPBlockTrie trie := ruleSet.IPBlockTrie
ip := net.ParseIP(remoteIP) ip := net.ParseIP(remoteIP)

View File

@ -6,7 +6,7 @@ import (
"server_torii/internal/dataType" "server_torii/internal/dataType"
) )
func URLAllowList(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision) { func URLAllowList(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision, sharedMem *dataType.SharedMemory) {
url := reqData.Uri url := reqData.Uri
list := ruleSet.URLAllowList list := ruleSet.URLAllowList
if list.Match(url) { if list.Match(url) {

View File

@ -6,7 +6,7 @@ import (
"server_torii/internal/dataType" "server_torii/internal/dataType"
) )
func URLBlockList(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision) { func URLBlockList(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision, sharedMem *dataType.SharedMemory) {
url := reqData.Uri url := reqData.Uri
list := ruleSet.URLBlockList list := ruleSet.URLBlockList
if list.Match(url) { if list.Match(url) {

View File

@ -9,7 +9,7 @@ import (
"strings" "strings"
) )
func VerifyBot(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision) { func VerifyBot(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *action.Decision, sharedMem *dataType.SharedMemory) {
ua := strings.ToLower(reqData.UserAgent) ua := strings.ToLower(reqData.UserAgent)
var exptractRDNS []string var exptractRDNS []string

View File

@ -235,8 +235,8 @@ func loadHTTPFloodRule(file string, rule *dataType.HTTPFloodRule) error {
return err return err
} }
rule.HTTPFloodSpeedLimit = make(map[int]int) rule.HTTPFloodSpeedLimit = make(map[int64]int64)
rule.HTTPFloodSameURILimit = make(map[int]int) rule.HTTPFloodSameURILimit = make(map[int64]int64)
for _, s := range ymlRule.HTTPFloodSpeedLimit { for _, s := range ymlRule.HTTPFloodSpeedLimit {
limit, seconds, err := utils.ParseRate(s) limit, seconds, err := utils.ParseRate(s)

View File

@ -27,9 +27,11 @@ type VerifyBotRule struct {
} }
type HTTPFloodRule struct { type HTTPFloodRule struct {
HTTPFloodSpeedLimit map[int]int HTTPFloodSpeedLimit map[int64]int64
HTTPFloodSameURILimit map[int]int HTTPFloodSameURILimit map[int64]int64
} }
type SharedMemory struct { type SharedMemory struct {
HTTPFloodSpeedLimitCounter *Counter
HTTPFloodSameURILimitCounter *Counter
} }

View File

@ -12,9 +12,9 @@ import (
"time" "time"
) )
type CheckFunc func(dataType.UserRequest, *config.RuleSet, *action.Decision) type CheckFunc func(dataType.UserRequest, *config.RuleSet, *action.Decision, *dataType.SharedMemory)
func CheckMain(w http.ResponseWriter, userRequestData dataType.UserRequest, ruleSet *config.RuleSet, cfg *config.MainConfig) { func CheckMain(w http.ResponseWriter, userRequestData dataType.UserRequest, ruleSet *config.RuleSet, cfg *config.MainConfig, sharedMem *dataType.SharedMemory) {
decision := action.NewDecision() decision := action.NewDecision()
checkFuncs := make([]CheckFunc, 0) checkFuncs := make([]CheckFunc, 0)
@ -23,10 +23,11 @@ func CheckMain(w http.ResponseWriter, userRequestData dataType.UserRequest, rule
checkFuncs = append(checkFuncs, check.URLAllowList) checkFuncs = append(checkFuncs, check.URLAllowList)
checkFuncs = append(checkFuncs, check.URLBlockList) checkFuncs = append(checkFuncs, check.URLBlockList)
checkFuncs = append(checkFuncs, check.VerifyBot) checkFuncs = append(checkFuncs, check.VerifyBot)
checkFuncs = append(checkFuncs, check.HTTPFlood)
checkFuncs = append(checkFuncs, check.Captcha) checkFuncs = append(checkFuncs, check.Captcha)
for _, checkFunc := range checkFuncs { for _, checkFunc := range checkFuncs {
checkFunc(userRequestData, ruleSet, decision) checkFunc(userRequestData, ruleSet, decision, sharedMem)
if decision.State == action.Done { if decision.State == action.Done {
break break
} }

View File

@ -10,15 +10,15 @@ import (
) )
// StartServer starts the HTTP server // StartServer starts the HTTP server
func StartServer(cfg *config.MainConfig, ruleSet *config.RuleSet) error { func StartServer(cfg *config.MainConfig, ruleSet *config.RuleSet, sharedMem *dataType.SharedMemory) error {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
userRequestData := processRequestData(cfg, r) userRequestData := processRequestData(cfg, r)
if strings.HasPrefix(userRequestData.Uri, cfg.WebPath) { if strings.HasPrefix(userRequestData.Uri, cfg.WebPath) {
CheckTorii(w, r, userRequestData, ruleSet, cfg) CheckTorii(w, r, userRequestData, ruleSet, cfg, sharedMem)
} else { } else {
CheckMain(w, userRequestData, ruleSet, cfg) CheckMain(w, userRequestData, ruleSet, cfg, sharedMem)
} }
}) })

View File

@ -12,7 +12,7 @@ import (
"time" "time"
) )
func CheckTorii(w http.ResponseWriter, r *http.Request, reqData dataType.UserRequest, ruleSet *config.RuleSet, cfg *config.MainConfig) { func CheckTorii(w http.ResponseWriter, r *http.Request, reqData dataType.UserRequest, ruleSet *config.RuleSet, cfg *config.MainConfig, sharedMem *dataType.SharedMemory) {
decision := action.NewDecision() decision := action.NewDecision()
decision.SetCode(action.Continue, []byte("403")) decision.SetCode(action.Continue, []byte("403"))

View File

@ -6,7 +6,7 @@ import (
"strings" "strings"
) )
func ParseRate(s string) (int, int, error) { func ParseRate(s string) (int64, int64, error) {
parts := strings.Split(s, "/") parts := strings.Split(s, "/")
if len(parts) != 2 { if len(parts) != 2 {
return 0, 0, fmt.Errorf("unexpected rate format: %s", s) return 0, 0, fmt.Errorf("unexpected rate format: %s", s)
@ -37,5 +37,5 @@ func ParseRate(s string) (int, int, error) {
default: default:
return 0, 0, fmt.Errorf("unexpected time unit: %s", string(unit)) return 0, 0, fmt.Errorf("unexpected time unit: %s", string(unit))
} }
return limit, seconds, nil return int64(limit), int64(seconds), nil
} }

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"os/signal" "os/signal"
"server_torii/internal/config" "server_torii/internal/config"
"server_torii/internal/dataType"
"server_torii/internal/server" "server_torii/internal/server"
"syscall" "syscall"
) )
@ -47,6 +48,10 @@ func main() {
log.SetOutput(logFile) log.SetOutput(logFile)
//allocate shared memory //allocate shared memory
sharedMem := &dataType.SharedMemory{
HTTPFloodSpeedLimitCounter: dataType.NewCounter(64, 60),
HTTPFloodSameURILimitCounter: dataType.NewCounter(64, 60),
}
// Start server // Start server
stop := make(chan os.Signal, 1) stop := make(chan os.Signal, 1)
@ -54,7 +59,7 @@ func main() {
serverErr := make(chan error, 1) serverErr := make(chan error, 1)
go func() { go func() {
serverErr <- server.StartServer(cfg, ruleSet) serverErr <- server.StartServer(cfg, ruleSet, sharedMem)
}() }()
select { select {