mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-25 16:04:38 +00:00
5e7317a818
## Description: On replays, there can be a burst of traffic from hashes, so instead just have a 2MB limit per client for the entire game. Also the winner message can be 100s of kb on a large game with many players, so now we don't need to put a special case for that. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
import { RateLimiter } from "limiter";
|
|
import { ClientID } from "../core/Schemas";
|
|
|
|
const INTENTS_PER_SECOND = 10;
|
|
const INTENTS_PER_MINUTE = 150;
|
|
const MAX_INTENT_SIZE = 500;
|
|
const TOTAL_BYTES = 2 * 1024 * 1024; // 2MB per client
|
|
export type RateLimitResult = "ok" | "limit" | "kick";
|
|
|
|
interface ClientBucket {
|
|
perSecond: RateLimiter;
|
|
perMinute: RateLimiter;
|
|
totalBytes: number;
|
|
}
|
|
|
|
export class ClientMsgRateLimiter {
|
|
private buckets = new Map<ClientID, ClientBucket>();
|
|
|
|
check(clientID: ClientID, type: string, bytes: number): RateLimitResult {
|
|
const bucket = this.getOrCreate(clientID);
|
|
bucket.totalBytes += bytes;
|
|
|
|
if (bucket.totalBytes >= TOTAL_BYTES) return "kick";
|
|
|
|
if (type === "intent") {
|
|
// Intents are stored in turn history for the duration of the game, so
|
|
// oversized intents would accumulate and fill up server RAM.
|
|
// Intents are also sent to all players, so it increase outgoing
|
|
// data.
|
|
// Intents should never be larger than MAX_INTENT_SIZE, so we assume the client is malicious.
|
|
if (bytes > MAX_INTENT_SIZE) {
|
|
return "kick";
|
|
}
|
|
if (
|
|
!bucket.perSecond.tryRemoveTokens(1) ||
|
|
!bucket.perMinute.tryRemoveTokens(1)
|
|
) {
|
|
return "limit";
|
|
}
|
|
}
|
|
|
|
return "ok";
|
|
}
|
|
|
|
private getOrCreate(clientID: ClientID): ClientBucket {
|
|
const existing = this.buckets.get(clientID);
|
|
if (existing) {
|
|
return existing;
|
|
}
|
|
const bucket = {
|
|
perSecond: new RateLimiter({
|
|
tokensPerInterval: INTENTS_PER_SECOND,
|
|
interval: "second",
|
|
}),
|
|
perMinute: new RateLimiter({
|
|
tokensPerInterval: INTENTS_PER_MINUTE,
|
|
interval: "minute",
|
|
}),
|
|
totalBytes: 0,
|
|
};
|
|
this.buckets.set(clientID, bucket);
|
|
return bucket;
|
|
}
|
|
}
|