3
0
mirror of https://github.com/ergochat/ergo.git synced 2025-12-08 17:07:36 +01:00

implement FAIL METADATA RATE_LIMITED

This commit is contained in:
Shivaram Lingamneni 2025-11-23 09:14:16 +00:00
parent 96aa018352
commit 0119bbc36f
8 changed files with 52 additions and 7 deletions

View File

@ -1111,6 +1111,11 @@ metadata:
max-subs: 100 max-subs: 100
# how many keys can be stored per entity? # how many keys can be stored per entity?
max-keys: 100 max-keys: 100
# rate limiting for client metadata updates, which are expensive to process
client-throttle:
enabled: true
duration: 2m
max-attempts: 10
# experimental support for mobile push notifications # experimental support for mobile push notifications
# see the manual for potential security, privacy, and performance implications. # see the manual for potential security, privacy, and performance implications.

View File

@ -130,6 +130,7 @@ type Client struct {
pushSubscriptionsExist atomic.Uint32 // this is a cache on len(pushSubscriptions) != 0 pushSubscriptionsExist atomic.Uint32 // this is a cache on len(pushSubscriptions) != 0
pushQueue pushQueue pushQueue pushQueue
metadata map[string]string metadata map[string]string
metadataThrottle connection_limits.ThrottleDetails
} }
type saslStatus struct { type saslStatus struct {

View File

@ -734,10 +734,11 @@ type Config struct {
} }
Metadata struct { Metadata struct {
Enabled bool Enabled bool
MaxSubs int `yaml:"max-subs"` MaxSubs int `yaml:"max-subs"`
MaxKeys int `yaml:"max-keys"` MaxKeys int `yaml:"max-keys"`
MaxValueBytes int `yaml:"max-value-length"` MaxValueBytes int `yaml:"max-value-length"`
ClientThrottle ThrottleConfig `yaml:"client-throttle"`
} }
WebPush struct { WebPush struct {

View File

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/ergochat/ergo/irc/caps" "github.com/ergochat/ergo/irc/caps"
"github.com/ergochat/ergo/irc/connection_limits"
"github.com/ergochat/ergo/irc/languages" "github.com/ergochat/ergo/irc/languages"
"github.com/ergochat/ergo/irc/modes" "github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/utils" "github.com/ergochat/ergo/irc/utils"
@ -1039,3 +1040,22 @@ func (client *Client) CountMetadata() int {
return len(client.metadata) return len(client.metadata)
} }
func (client *Client) checkMetadataThrottle() (throttled bool, remainingTime time.Duration) {
config := client.server.Config()
if !config.Metadata.ClientThrottle.Enabled {
return false, 0
}
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
// copy client.metadataThrottle locally and then back for processing
var throttle connection_limits.GenericThrottle
throttle.ThrottleDetails = client.metadataThrottle
throttle.Duration = config.Metadata.ClientThrottle.Duration
throttle.Limit = config.Metadata.ClientThrottle.MaxAttempts
throttled, remainingTime = throttle.Touch()
client.metadataThrottle = throttle.ThrottleDetails
return
}

View File

@ -3197,6 +3197,18 @@ func metadataRegisteredHandler(client *Client, config *Config, subcommand string
return return
} }
// only rate limit clients changing their own metadata:
// channel metadata updates are not any more costly than a PRIVMSG
if client == targetClient {
if throttled, remainingTime := client.checkMetadataThrottle(); throttled {
retryAfter := strconv.Itoa(int(remainingTime.Seconds()) + 1)
rb.Add(nil, server.name, "FAIL", "METADATA", "RATE_LIMITED",
target, utils.SafeErrorParam(key), retryAfter,
fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime.Round(time.Millisecond)))
return
}
}
if len(params) > 3 { if len(params) > 3 {
value := params[3] value := params[3]

View File

@ -21,7 +21,7 @@ var (
errMetadataNotFound = errors.New("key not found") errMetadataNotFound = errors.New("key not found")
) )
type MetadataHaver = interface { type MetadataHaver interface {
SetMetadata(key string, value string, limit int) (updated bool, err error) SetMetadata(key string, value string, limit int) (updated bool, err error)
GetMetadata(key string) (string, bool) GetMetadata(key string) (string, bool)
DeleteMetadata(key string) (updated bool) DeleteMetadata(key string) (updated bool)

View File

@ -183,12 +183,13 @@ const (
RPL_MONLIST = "732" RPL_MONLIST = "732"
RPL_ENDOFMONLIST = "733" RPL_ENDOFMONLIST = "733"
ERR_MONLISTFULL = "734" ERR_MONLISTFULL = "734"
RPL_KEYVALUE = "761" // metadata numerics RPL_WHOISKEYVALUE = "760" // metadata numerics
RPL_KEYVALUE = "761"
RPL_KEYNOTSET = "766" RPL_KEYNOTSET = "766"
RPL_METADATASUBOK = "770" RPL_METADATASUBOK = "770"
RPL_METADATAUNSUBOK = "771" RPL_METADATAUNSUBOK = "771"
RPL_METADATASUBS = "772" RPL_METADATASUBS = "772"
RPL_METADATASYNCLATER = "774" RPL_METADATASYNCLATER = "774" // end metadata numerics
RPL_LOGGEDIN = "900" RPL_LOGGEDIN = "900"
RPL_LOGGEDOUT = "901" RPL_LOGGEDOUT = "901"
ERR_NICKLOCKED = "902" ERR_NICKLOCKED = "902"

View File

@ -1082,6 +1082,11 @@ metadata:
max-subs: 100 max-subs: 100
# how many keys can be stored per entity? # how many keys can be stored per entity?
max-keys: 100 max-keys: 100
# rate limiting for client metadata updates, which are expensive to process
client-throttle:
enabled: true
duration: 2m
max-attempts: 10
# experimental support for mobile push notifications # experimental support for mobile push notifications
# see the manual for potential security, privacy, and performance implications. # see the manual for potential security, privacy, and performance implications.