feat: add rate limiting

The server will wait 1s if clients:
 - repeat the same request (same `?pos=`)
 - repeatedly hit `/sync` without a `?pos=`.

Both of these failure modes have been seen in the wild.
Fixes #93.
This commit is contained in:
Kegan Dougal 2023-05-22 17:44:04 +01:00
parent 8c1ef09d65
commit afaea53064
3 changed files with 21 additions and 2 deletions

View File

@ -5,11 +5,17 @@ import (
"fmt"
"runtime/debug"
"sync"
"time"
"github.com/matrix-org/sliding-sync/internal"
"github.com/matrix-org/sliding-sync/sync3/caches"
)
// The amount of time to artificially wait if the server detects spamming clients. This time will
// be added to responses when the server detects the same request being sent over and over e.g
// /sync?pos=5 then /sync?pos=5 over and over. Likewise /sync without a ?pos=.
var SpamProtectionInterval = time.Second
type ConnID struct {
UserID string
DeviceID string
@ -176,7 +182,10 @@ func (c *Conn) OnIncomingRequest(ctx context.Context, req *Request) (resp *Respo
if isSameRequest {
// this is the 2nd+ time we've seen this request, meaning the client likely retried this
// request. Send the response we sent before.
logger.Trace().Int64("pos", req.pos).Msg("returning cached response for pos")
logger.Trace().Int64("pos", req.pos).Msg("returning cached response for pos, with delay")
// apply a small artificial wait to protect the proxy in case this is caused by a buggy
// client sending the same request over and over
time.Sleep(SpamProtectionInterval)
return nextUnACKedResponse, nil
} else {
logger.Info().Int64("pos", req.pos).Msg("client has resent this pos with different request data")

View File

@ -77,7 +77,14 @@ func (m *ConnMap) CreateConn(cid ConnID, newConnHandler func() ConnHandler) (*Co
conn := m.Conn(cid)
if conn != nil {
// tear down this connection and fallthrough
logger.Trace().Str("conn", cid.String()).Msg("closing connection due to CreateConn called again")
isSpamming := conn.lastPos <= 1
if isSpamming {
// the existing connection has only just been used for one response, and now they are asking
// for a new connection. Apply an artificial delay here to stop buggy clients from spamming
// /sync without a `?pos=` value.
time.Sleep(SpamProtectionInterval)
}
logger.Trace().Str("conn", cid.String()).Bool("spamming", isSpamming).Msg("closing connection due to CreateConn called again")
m.closeConn(conn)
}
h := newConnHandler()

View File

@ -363,6 +363,9 @@ func runTestServer(t testutils.TestBenchInterface, v2Server *testV2Server, postg
if postgresConnectionString == "" {
postgresConnectionString = testutils.PrepareDBConnectionString()
}
//tests often repeat requests. To ensure tests remain fast, reduce the spam protection limits.
sync3.SpamProtectionInterval = time.Millisecond
metricsEnabled := false
maxPendingEventUpdates := 200
if len(opts) > 0 {