3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-22 11:59:40 +01:00

Merge pull request #1993 from slingamn/fakelag_budget.3

exempt a configurable number of MARKREAD commands from fakelag
This commit is contained in:
Shivaram Lingamneni 2022-08-22 20:26:11 -07:00 committed by GitHub
commit 0e8f447326
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 59 additions and 12 deletions

View File

@ -849,6 +849,14 @@ fakelag:
# sending any commands: # sending any commands:
cooldown: 2s cooldown: 2s
# exempt a certain number of command invocations per session from fakelag;
# this is to speed up "resynchronization" of client state during reattach
command-budgets:
"CHATHISTORY": 16
"MARKREAD": 16
"MONITOR": 1
"WHO": 4
# the roleplay commands are semi-standardized extensions to IRC that allow # the roleplay commands are semi-standardized extensions to IRC that allow
# sending and receiving messages from pseudo-nicknames. this can be used either # sending and receiving messages from pseudo-nicknames. this can be used either
# for actual roleplaying, or for bridging IRC with other protocols. # for actual roleplaying, or for bridging IRC with other protocols.

View File

@ -668,12 +668,21 @@ func (client *Client) run(session *Session) {
} }
} }
msg, err := ircmsg.ParseLineStrict(line, true, MaxLineLen)
// XXX defer processing of command error parsing until after fakelag
if client.registered { if client.registered {
touches := session.deferredFakelagCount + 1 // apply deferred fakelag
session.deferredFakelagCount = 0 for i := 0; i < session.deferredFakelagCount; i++ {
for i := 0; i < touches; i++ { session.fakelag.Touch("")
session.fakelag.Touch()
} }
session.deferredFakelagCount = 0
// touch for the current command
var command string
if err == nil {
command = msg.Command
}
session.fakelag.Touch(command)
} else { } else {
// DoS hardening, #505 // DoS hardening, #505
session.registrationMessages++ session.registrationMessages++
@ -683,7 +692,6 @@ func (client *Client) run(session *Session) {
} }
} }
msg, err := ircmsg.ParseLineStrict(line, true, MaxLineLen)
if err == ircmsg.ErrorLineIsEmpty { if err == ircmsg.ErrorLineIsEmpty {
continue continue
} else if err == ircmsg.ErrorTagsTooLong { } else if err == ircmsg.ErrorTagsTooLong {

View File

@ -524,6 +524,7 @@ type FakelagConfig struct {
BurstLimit uint `yaml:"burst-limit"` BurstLimit uint `yaml:"burst-limit"`
MessagesPerWindow uint `yaml:"messages-per-window"` MessagesPerWindow uint `yaml:"messages-per-window"`
Cooldown time.Duration Cooldown time.Duration
CommandBudgets map[string]int `yaml:"command-budgets"`
} }
type TorListenersConfig struct { type TorListenersConfig struct {
@ -1428,6 +1429,17 @@ func LoadConfig(filename string) (config *Config, err error) {
} }
config.Server.capValues[caps.Languages] = config.languageManager.CapValue() config.Server.capValues[caps.Languages] = config.languageManager.CapValue()
if len(config.Fakelag.CommandBudgets) != 0 {
// normalize command names to uppercase:
commandBudgets := make(map[string]int, len(config.Fakelag.CommandBudgets))
for command, budget := range config.Fakelag.CommandBudgets {
commandBudgets[strings.ToUpper(command)] = budget
}
config.Fakelag.CommandBudgets = commandBudgets
} else {
config.Fakelag.CommandBudgets = nil
}
if config.Server.Relaymsg.Enabled { if config.Server.Relaymsg.Enabled {
for _, char := range protocolBreakingNameCharacters { for _, char := range protocolBreakingNameCharacters {
if strings.ContainsRune(config.Server.Relaymsg.Separators, char) { if strings.ContainsRune(config.Server.Relaymsg.Separators, char) {

View File

@ -5,6 +5,8 @@ package irc
import ( import (
"time" "time"
"github.com/ergochat/ergo/irc/utils"
) )
// fakelag is a system for artificially delaying commands when a user issues // fakelag is a system for artificially delaying commands when a user issues
@ -36,6 +38,10 @@ type Fakelag struct {
func (fl *Fakelag) Initialize(config FakelagConfig) { func (fl *Fakelag) Initialize(config FakelagConfig) {
fl.config = config fl.config = config
// XXX don't share mutable member CommandBudgets:
if config.CommandBudgets != nil {
fl.config.CommandBudgets = utils.CopyMap(config.CommandBudgets)
}
fl.nowFunc = time.Now fl.nowFunc = time.Now
fl.sleepFunc = time.Sleep fl.sleepFunc = time.Sleep
fl.state = FakelagBursting fl.state = FakelagBursting
@ -58,11 +64,16 @@ func (fl *Fakelag) Unsuspend() {
} }
// register a new command, sleep if necessary to delay it // register a new command, sleep if necessary to delay it
func (fl *Fakelag) Touch() { func (fl *Fakelag) Touch(command string) {
if !fl.config.Enabled { if !fl.config.Enabled {
return return
} }
if budget, ok := fl.config.CommandBudgets[command]; ok && budget > 0 {
fl.config.CommandBudgets[command] = budget - 1
return
}
now := fl.nowFunc() now := fl.nowFunc()
// XXX if lastTouch.IsZero(), treat it as "very far in the past", which is fine // XXX if lastTouch.IsZero(), treat it as "very far in the past", which is fine
elapsed := now.Sub(fl.lastTouch) elapsed := now.Sub(fl.lastTouch)

View File

@ -60,7 +60,7 @@ func TestFakelag(t *testing.T) {
window, _ := time.ParseDuration("1s") window, _ := time.ParseDuration("1s")
fl, mt := newFakelagForTesting(window, 3, 2, window) fl, mt := newFakelagForTesting(window, 3, 2, window)
fl.Touch() fl.Touch("")
slept, _ := mt.lastSleep() slept, _ := mt.lastSleep()
if slept { if slept {
t.Fatalf("should not have slept") t.Fatalf("should not have slept")
@ -69,7 +69,7 @@ func TestFakelag(t *testing.T) {
interval, _ := time.ParseDuration("100ms") interval, _ := time.ParseDuration("100ms")
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
mt.pause(interval) mt.pause(interval)
fl.Touch() fl.Touch("")
slept, _ := mt.lastSleep() slept, _ := mt.lastSleep()
if slept { if slept {
t.Fatalf("should not have slept") t.Fatalf("should not have slept")
@ -77,7 +77,7 @@ func TestFakelag(t *testing.T) {
} }
mt.pause(interval) mt.pause(interval)
fl.Touch() fl.Touch("")
if fl.state != FakelagThrottled { if fl.state != FakelagThrottled {
t.Fatalf("should be throttled") t.Fatalf("should be throttled")
} }
@ -91,7 +91,7 @@ func TestFakelag(t *testing.T) {
} }
// send another message without a pause; we should have to sleep for 500 msec // send another message without a pause; we should have to sleep for 500 msec
fl.Touch() fl.Touch("")
if fl.state != FakelagThrottled { if fl.state != FakelagThrottled {
t.Fatalf("should be throttled") t.Fatalf("should be throttled")
} }
@ -102,7 +102,7 @@ func TestFakelag(t *testing.T) {
} }
mt.pause(interval * 6) mt.pause(interval * 6)
fl.Touch() fl.Touch("")
if fl.state != FakelagThrottled { if fl.state != FakelagThrottled {
t.Fatalf("should still be throttled") t.Fatalf("should still be throttled")
} }
@ -112,7 +112,7 @@ func TestFakelag(t *testing.T) {
} }
mt.pause(window * 2) mt.pause(window * 2)
fl.Touch() fl.Touch("")
if fl.state != FakelagBursting { if fl.state != FakelagBursting {
t.Fatalf("should be bursting again") t.Fatalf("should be bursting again")
} }

View File

@ -821,6 +821,14 @@ fakelag:
# sending any commands: # sending any commands:
cooldown: 2s cooldown: 2s
# exempt a certain number of command invocations per session from fakelag;
# this is to speed up "resynchronization" of client state during reattach
command-budgets:
"CHATHISTORY": 16
"MARKREAD": 16
"MONITOR": 1
"WHO": 4
# the roleplay commands are semi-standardized extensions to IRC that allow # the roleplay commands are semi-standardized extensions to IRC that allow
# sending and receiving messages from pseudo-nicknames. this can be used either # sending and receiving messages from pseudo-nicknames. this can be used either
# for actual roleplaying, or for bridging IRC with other protocols. # for actual roleplaying, or for bridging IRC with other protocols.