Hello there!
Exploiters often try to spam your remotes to see if they exceed the limit the server can handle and causing it to crash.
In general, it is good practice to limit the rate of your remotes to prevent that behavior. This module should make this process very easy for you.
What it does:
This module I made allows you to easily set a maximum request rate for your RemoteEvents or RemoteFunctions. With this module, you can simply ignore requests if your set maximum rate is exceeded by the user.
How it works:
The module counts every request made by the player through a specified remote and determines whether or not the request should be processed by the listener.
Easy implementation:
local FloodCheck = require(game.ServerScriptService.RemoteFloodCheck) -- Reference the module
MyEvent.OnServerEvent:Connect(function(Player)
if FloodCheck:Check(Player, MyEvent, 10) then --< This returns true if the request should be processed.
-- Process the request
end
end
Features:
Module logs the player that fires the remote and the remote that was fired itself. This means that if player A throttles a remote, player B can still use it.
The module changes behavior depending on the maximum rate that is set by you (or default rate if left unspecified). If you have a rate set to higher than 1 per second, letâs say 10 per second, the script will let the first 10 requests through regardless of time in between and then block all the consecutive requests that were made in that second. However, if you have, letâs say a maximum rate of 1 request per 5 seconds, it blocks all the requests that were made within the first 5 seconds after the first request.
The module can be used on multiple scripts listening to the same remote at once without them counting as duplicates (and then filling up the number of requests the player can make). This is useful when dealing with packages or clones of scripts that all listen to the same remote.
The module has a Unique_Identifier feature that allows you to assign separate MaxRates to different scripts that are listening to the remote at the same time. This can be used to throttle more resource-intensive code more harshly than easier to run more critical code.
Source code (with extra documentation on how to implement):
Code (to be used in a ModuleScript)
-->> Settings
local DefaultMaxRatePerSec = 10
local DefaultIdentifier = "Identifier"
-->>
-- Made by XandertjeKnal
--[[ Documentation:
This module can be used to make sure rates of Remotes are not exceeded.
FloodCheck:Check(Player_Object, Event, RatePerSec, Unique_Identifier) -- Will return false if the limit is exceeded, otherwise will return true.
- Player_Object
Reference the player that is making the request.
- Event
Reference the event that you are listening to.
- RatePerSec:
RatePerSec > 1
If the RatePerSec variable x is set higher than 1, it will allow x requests in the first second after the first request was made. E.g. if RatePerSec is set to 5, a player can send 5 consecutive requests in any time that is less than a second, for example within the first 200ms even. All other requests in that second will be blocked. After that second, the request count will be reset.
RatePerSec <= 1
If the RatePerSec variable x is set lower or equal to 1, it will only allow a consecutive request (1/x) seconds after the first request. E.g. if RatePerSec is set to 0.2, a player has to wait (1/0.2) = 5 seconds after sending the first request before any consecutive requests will be processed. All other requests in between will be blocked.
The RatePerSec should not be changed after the first request has been checked.
If left nil, the DefaultMaxRatePerSec will be used.
- Unique_Identifier:
This variable is optional to use and can most often be left nil. You only have to use this if there are multiple scripts listening to the same RemoteEvent/RemoteFunction.
E.g. 2 scripts are listening to an 'ExplodeTNT' event. You can set one Unique_Identifier to '1' and the other to '2'. If you are using clones of the same script (or packages) listening to the same event then you may want to use a random number or string that is randomized only once(!) per script as the Unique_Identifier instead.
This identifier is used to prevent multiple checks from filling up the flood check.
You could in theory also use this identifier to have different Rates for different scripts listening to the same remote. Example usage: throttle resource-intensive scripts more than the game-critical scripts, even if they are listening to the same event.
- Example usage:
local FloodCheck = require(game.ServerScriptService.RemoteFloodCheck) -- Reference this module
MyEvent.OnServerEvent:Connect(function(Player)
if FloodCheck:Check(Player, MyEvent, 10) then
-- Process the request
end
end
--]]
local FloodCheck = {}
local Requests = {}
function FloodCheck:Check(Player_Object, Remote, RatePerSec, Unique_Identifier)
local Rate = RatePerSec or DefaultMaxRatePerSec
local Identifier = Unique_Identifier or DefaultIdentifier
if not Requests[Player_Object] then
Requests[Player_Object] = {}
local connection
local function PlayerLeft(player)
if player == Player_Object then
Requests[Player_Object] = nil
connection:Disconnect()
end
end
connection = game.Players.PlayerRemoving:Connect(PlayerLeft)
end
if not Requests[Player_Object][Remote] then
Requests[Player_Object][Remote] = {}
end
if not Requests[Player_Object][Remote][Identifier] then
Requests[Player_Object][Remote][Identifier] = {}
end
if Rate > 1 then
if Requests[Player_Object][Remote][Identifier]["Count"] then
local TimeElapsed = tick() - Requests[Player_Object][Remote][Identifier]["StartTime"]
if TimeElapsed >= 1 then
Requests[Player_Object][Remote][Identifier]["Count"] = 1
Requests[Player_Object][Remote][Identifier]["StartTime"] = tick()
return true
else
Requests[Player_Object][Remote][Identifier]["Count"] = Requests[Player_Object][Remote][Identifier]["Count"] + 1
return Requests[Player_Object][Remote][Identifier]["Count"] <= Rate
end
else
Requests[Player_Object][Remote][Identifier]["Count"] = 1
Requests[Player_Object][Remote][Identifier]["StartTime"] = tick()
return true
end
end
if Rate <= 1 then
if Requests[Player_Object][Remote][Identifier]["LastTime"] then
local TimeElapsed = tick() - Requests[Player_Object][Remote][Identifier]["LastTime"]
if TimeElapsed >= (1/Rate) then
Requests[Player_Object][Remote][Identifier]["LastTime"] = tick()
return true
else
return false
end
else
Requests[Player_Object][Remote][Identifier]["LastTime"] = tick()
return true
end
end
end
return FloodCheck
This is a contribution that actually has a real-world use, nice job, I usually implement this on my own, but having a module will be nicer to add and to modify.
The module can be used on the client, yes. Keep in mind that this will only limit your own LocalScripts from firing remotes. It will not prevent exploiters from firing them. To prevent that, you will have to use the module on the server in scripts that listen to the remotes, so the requests can be blocked there.
Because the connection is not disconnected, it will be called more times than it should. If you keep firing the function the event is going to be ran another time.
I canât really explain this too well, this thread is a good read.
afaik it shouldnât impact other clients, it doesnât make sense to compromise other users when they arenât involved.
Although Roblox does monitor this itâs for a different purpose and does a different thing, which thatâs not the point of having this in your game, itâs to filter out Exploiters from Legitimate Clients.
Roblox has no way to know if a remote is being âover-throttledâ or not; some remotes are supposed to be fired a lot, some are not.
The point of throttling the remotes yourself is to reduce the stress on the server that may be caused when clients trigger a resource-heavy task multiple times (and too frequently!).
The point @RuizuKun_Dev made is also valid. When, for instance, an exploiter fires a remote too many times, it should not negatively impact the experience for the other players (by having the server crash or by disabling the function attached to the remote completely). This was the main motivation behind making and publishing this module.
This post has often been quoted in the context of other networking problems.
I recently wrote this reply for someone that goes more in-depth on ping, lag, and on how you could tackle some of the other networking problems related to guns in an FPS game:
Unless I misunderstand, isnât this kind of completely pointless? Anybody can limit players from proceeding in the event with an if statement (and not having to wrap in a pointless function abstraction that uncessarily uses self )
To actually rate limit, i.e block the server from responding to a certain players request at all, is far different. Because as it stands if that player is exploiting and firing the remote hundreds of times a second, this will do nothing besides stop the server code from executing, in fact because of your module implementation you may end up causing server lag.
Indeed, this module limits the code on the server from executing if a rate is exceeded, therefore reducing stress on the server. This practice is very common and I recommend that you look more into the subject.
This claim is not true at all, all my module does is relatively inexpensive, e.g. manipulating a table. (As you can see in the source code.)
This doesnât reduce stress on the server, sure, itâll stop the code you want to execute from executing, but you donât seem to know what I mean. What I mean is, if they fire that remote hundreds of times, that event will still fire hundreds of times, that if statement will still conditionally check hundreds of times. Nothing has changed besides the extent to which the player proceeds to. And itâs already too far.
This claim is not true at all, all my module does is relatively inexpensive, e.g. manipulating a table. (As you can see in the source code.)
Iâm pretty sure I said the wrong thing on this part, while you may not cause server lag per se, wrapping this in a, again uneeded function call that also doesnât use self but has it there anyway. It will be more expensive than literally just having only an if statement.
So instead of making wrappers for something that doesnât actually save any resources, (if anything, the opposite effect compared to standard implementation of this) Iâd suggest you instead make a tutorial advising people on this practice of stopping remote abuse.
The module works for both RemoteEvents and RemoteFunctions!
In the source code, there are detailed instructions on how to use my module + code examples.
I think youâre confused about the expense of function calls - i.e. the lack thereof of any actual cost.
What youâre suggesting is sanity checks (I think?) - those should be included on top of a module like this. Using catch all solutions, like a rate limiting wrapper, is good practice because it acts as a safeguard against accidental failure to implement proper controls (the risk of accidentally allowing a DoS-style attack is increased by relying on sanity checks on every single remote, rather than wrapping them in a module which sorts it for you).
The cost to something like this is minimal, if not plain negligible, and the benefit is pretty clear from a security and performance perspective.
It wasnât clear from here if I could check if the player is being throttled from a LocalScript (to for instance notify them of this) without it limiting the amount of uses a player gets.
To be more precise, Iâm thinking of adding a sort of âPanic Buttonâ function to a custom chat script, and using this to not only prevent spamming remote events but also implement the delay of 20 seconds, or (0.1 allowed times per second)