From ad82c0dca3b2a4aeecae5e35858907056bbae068 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 3 Jul 2022 20:43:40 -0400 Subject: [PATCH 1/3] exempt a configurable number of MARKREAD commands from fakelag --- default.yaml | 6 ++++++ irc/client.go | 16 ++++++++++++---- irc/config.go | 12 ++++++++++++ irc/fakelag.go | 13 ++++++++++++- irc/fakelag_test.go | 12 ++++++------ traditional.yaml | 6 ++++++ 6 files changed, 54 insertions(+), 11 deletions(-) diff --git a/default.yaml b/default.yaml index 8da88e09e..d06dc9af4 100644 --- a/default.yaml +++ b/default.yaml @@ -849,6 +849,12 @@ fakelag: # sending any commands: 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: + "MARKREAD": 16 + "CHATHISTORY": 16 + # the roleplay commands are semi-standardized extensions to IRC that allow # sending and receiving messages from pseudo-nicknames. this can be used either # for actual roleplaying, or for bridging IRC with other protocols. diff --git a/irc/client.go b/irc/client.go index debc8b1e4..a16c6abe3 100644 --- a/irc/client.go +++ b/irc/client.go @@ -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 { - touches := session.deferredFakelagCount + 1 + // apply deferred fakelag + for i := 0; i < session.deferredFakelagCount; i++ { + session.fakelag.Touch("") + } session.deferredFakelagCount = 0 - for i := 0; i < touches; i++ { - session.fakelag.Touch() + // touch for the current command + var command string + if err == nil { + command = msg.Command } + session.fakelag.Touch(command) } else { // DoS hardening, #505 session.registrationMessages++ @@ -683,7 +692,6 @@ func (client *Client) run(session *Session) { } } - msg, err := ircmsg.ParseLineStrict(line, true, MaxLineLen) if err == ircmsg.ErrorLineIsEmpty { continue } else if err == ircmsg.ErrorTagsTooLong { diff --git a/irc/config.go b/irc/config.go index 75fd1f273..9e9cb8778 100644 --- a/irc/config.go +++ b/irc/config.go @@ -524,6 +524,7 @@ type FakelagConfig struct { BurstLimit uint `yaml:"burst-limit"` MessagesPerWindow uint `yaml:"messages-per-window"` Cooldown time.Duration + CommandBudgets map[string]int `yaml:"command-budgets"` } type TorListenersConfig struct { @@ -1428,6 +1429,17 @@ func LoadConfig(filename string) (config *Config, err error) { } 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 { for _, char := range protocolBreakingNameCharacters { if strings.ContainsRune(config.Server.Relaymsg.Separators, char) { diff --git a/irc/fakelag.go b/irc/fakelag.go index 8f3a4c9ec..25ef0609d 100644 --- a/irc/fakelag.go +++ b/irc/fakelag.go @@ -5,6 +5,8 @@ package irc import ( "time" + + "github.com/ergochat/ergo/irc/utils" ) // 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) { 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.sleepFunc = time.Sleep fl.state = FakelagBursting @@ -58,11 +64,16 @@ func (fl *Fakelag) Unsuspend() { } // register a new command, sleep if necessary to delay it -func (fl *Fakelag) Touch() { +func (fl *Fakelag) Touch(command string) { if !fl.config.Enabled { return } + if budget, ok := fl.config.CommandBudgets[command]; ok && budget > 0 { + fl.config.CommandBudgets[command] = budget - 1 + return + } + now := fl.nowFunc() // XXX if lastTouch.IsZero(), treat it as "very far in the past", which is fine elapsed := now.Sub(fl.lastTouch) diff --git a/irc/fakelag_test.go b/irc/fakelag_test.go index bc1ff45b9..e10794665 100644 --- a/irc/fakelag_test.go +++ b/irc/fakelag_test.go @@ -60,7 +60,7 @@ func TestFakelag(t *testing.T) { window, _ := time.ParseDuration("1s") fl, mt := newFakelagForTesting(window, 3, 2, window) - fl.Touch() + fl.Touch("") slept, _ := mt.lastSleep() if slept { t.Fatalf("should not have slept") @@ -69,7 +69,7 @@ func TestFakelag(t *testing.T) { interval, _ := time.ParseDuration("100ms") for i := 0; i < 2; i++ { mt.pause(interval) - fl.Touch() + fl.Touch("") slept, _ := mt.lastSleep() if slept { t.Fatalf("should not have slept") @@ -77,7 +77,7 @@ func TestFakelag(t *testing.T) { } mt.pause(interval) - fl.Touch() + fl.Touch("") if fl.state != FakelagThrottled { 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 - fl.Touch() + fl.Touch("") if fl.state != FakelagThrottled { t.Fatalf("should be throttled") } @@ -102,7 +102,7 @@ func TestFakelag(t *testing.T) { } mt.pause(interval * 6) - fl.Touch() + fl.Touch("") if fl.state != FakelagThrottled { t.Fatalf("should still be throttled") } @@ -112,7 +112,7 @@ func TestFakelag(t *testing.T) { } mt.pause(window * 2) - fl.Touch() + fl.Touch("") if fl.state != FakelagBursting { t.Fatalf("should be bursting again") } diff --git a/traditional.yaml b/traditional.yaml index 7ea271371..125e14b6b 100644 --- a/traditional.yaml +++ b/traditional.yaml @@ -821,6 +821,12 @@ fakelag: # sending any commands: 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: + "MARKREAD": 16 + "CHATHISTORY": 16 + # the roleplay commands are semi-standardized extensions to IRC that allow # sending and receiving messages from pseudo-nicknames. this can be used either # for actual roleplaying, or for bridging IRC with other protocols. From cb5e7c7cb9cb0f87c3cc069b94b42890b2cd7314 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Wed, 13 Jul 2022 15:58:50 -0400 Subject: [PATCH 2/3] allow a free MONITOR invocation as well --- default.yaml | 3 ++- traditional.yaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/default.yaml b/default.yaml index d06dc9af4..88d7fef21 100644 --- a/default.yaml +++ b/default.yaml @@ -852,8 +852,9 @@ fakelag: # exempt a certain number of command invocations per session from fakelag; # this is to speed up "resynchronization" of client state during reattach command-budgets: - "MARKREAD": 16 "CHATHISTORY": 16 + "MARKREAD": 16 + "MONITOR": 1 # the roleplay commands are semi-standardized extensions to IRC that allow # sending and receiving messages from pseudo-nicknames. this can be used either diff --git a/traditional.yaml b/traditional.yaml index 125e14b6b..1c3f78838 100644 --- a/traditional.yaml +++ b/traditional.yaml @@ -824,8 +824,9 @@ fakelag: # exempt a certain number of command invocations per session from fakelag; # this is to speed up "resynchronization" of client state during reattach command-budgets: - "MARKREAD": 16 "CHATHISTORY": 16 + "MARKREAD": 16 + "MONITOR": 1 # the roleplay commands are semi-standardized extensions to IRC that allow # sending and receiving messages from pseudo-nicknames. this can be used either From 71da08edce4494bc466b41eea37133490477dd0d Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 22 Aug 2022 23:10:09 -0400 Subject: [PATCH 3/3] add some free WHO invocations --- default.yaml | 1 + traditional.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/default.yaml b/default.yaml index 88d7fef21..504255525 100644 --- a/default.yaml +++ b/default.yaml @@ -855,6 +855,7 @@ fakelag: "CHATHISTORY": 16 "MARKREAD": 16 "MONITOR": 1 + "WHO": 4 # the roleplay commands are semi-standardized extensions to IRC that allow # sending and receiving messages from pseudo-nicknames. this can be used either diff --git a/traditional.yaml b/traditional.yaml index 1c3f78838..3ea879ae4 100644 --- a/traditional.yaml +++ b/traditional.yaml @@ -827,6 +827,7 @@ fakelag: "CHATHISTORY": 16 "MARKREAD": 16 "MONITOR": 1 + "WHO": 4 # the roleplay commands are semi-standardized extensions to IRC that allow # sending and receiving messages from pseudo-nicknames. this can be used either