Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to limit client triggered events (via triggerServerEvent) #3251

Merged
merged 2 commits into from Nov 27, 2023

Conversation

Lpsd
Copy link
Member

@Lpsd Lpsd commented Nov 26, 2023

Currently, one of the only ways to limit the amount of events being sent from the client to server (via triggerServerEvent) is to track the calls manually for every event handler you add (in Lua) which results in a valid client variable being set, upon the handler function being executed.

That's fine for registered events (although a bit of a pain to add for every single event). However, unregistered events cannot be tracked this way. The only way to track unregistered events being called is by using onDebugMessage and catching a log entry such as below, which isn't ideal:

Client (lopsi) triggered serverside event myTestEvent, but event is not added serverside

This is also the same for events which do exist, but aren't remotely triggerable:

Client (lopsi) triggered serverside event myTestEvent, but event is not marked as remotely triggerable

A client spamming serverside events can cause lag, so I've added a way to deal with this in MTA itself.

This PR adds a new serverside event, onPlayerTriggerEventThreshold, as well as two new mtaserver.conf settings:

  • player_triggered_event_interval
  • max_player_triggered_events_per_interval

These options can be set using setServerConfigSetting in Lua, or directly via mtaserver.conf before server startup.

Providing the player has triggered more than max_player_triggered_events_per_interval events per player_triggered_event_interval time (in milliseconds), the event will be fired, giving full control to the server how they deal with such players (log, kick, ban, etc).

This event counts ALL events triggered by the client, whether they are registered, unregistered or not remotely triggerable. Basically any usage of triggerServerEvent by the client will be counted.

Usage is fairly self-explanatory:

function processPlayerTriggerEventThreshold()
    kickPlayer(source, "Event spam")
end
addEventHandler("onPlayerTriggerEventThreshold", root, processPlayerTriggerEventThreshold)

I've also changed the way mtaserver.conf.template is loaded in the code, as adding these options took it over the 16,384 char string literal limit. That file will now be copied into the server deathmatch directory and read as XML directly, instead of const char* szTemplateText = #include MTA_SERVER_CONF_TEMPLATE; shivers

As it's currently possible for a client to abuse this event spamming, it's important that all server owners become aware of this and take necessary measures to implement (atleast) the example script above, once this PR has been merged.

Until then, I have attached a Lua script which will prevent unregistered events from being spammed (default max 10 per 500 ms). Feel free to export the registerEventUsage function and track your own registered events in other resources too.

Download the resource/script here: events.zip

@TheNormalnij
Copy link
Contributor

TheNormalnij commented Nov 26, 2023

I didn't try my code. But i can simply implement token bucket in Lua:

local UPDATE_INTERVAL = 100
local LIMIT_PER_SECOND = 100

local REFRESH_COUNT = LIMIT_PER_SECOND / 1000 * UPDATE_INTERVAL

local limitsData = {}

local function update()
    for player, count in pairs(limitsData) do
        local newCount = count + REFRESH_COUNT
        if newCount > LIMIT_PER_SECOND then
            limitsData[player] = LIMIT_PER_SECOND
        else
            limitsData[player] = newCount
        end
    end
end
setTimer(update, UPDATE_INTERVAL, 0)

local function init()
    for _, player in pairs(getElementsByType("player")) do
        limitsData[player] = LIMIT_PER_SECOND
    end
end

init()

addEventHandler("onPlayerJoin", root, function()
    limitsData[source] = LIMIT_PER_SECOND
end)

addEventHandler("onPlayerQuit", root, function()
    limitsData[source] = nil
end)

addDebugHook("postEvent", function(sourceResource, eventName, eventSource, eventClient)
    if eventClient then
        local newCount = limitsData[eventClient] - 1
        if newCount >= 0 then
            limitsData[eventClient] = newCount
        else
            outputDebugString("Kick player during event spam. Latest event: " .. eventName, 3)
            kickPlayer(eventClient, "Event spam")
        end
    end
end)

@Lpsd
Copy link
Member Author

Lpsd commented Nov 26, 2023

You still have the problem that unregistered events and non-remotely triggerable events will not be caught by your code.

Server/mods/deathmatch/logic/CGame.cpp Outdated Show resolved Hide resolved
Server/mods/deathmatch/logic/CGame.cpp Outdated Show resolved Hide resolved
@Dutchman101 Dutchman101 merged commit eae47fe into multitheftauto:master Nov 27, 2023
6 checks passed
MTABot pushed a commit that referenced this pull request Nov 27, 2023
eae47fe Add ability to limit client triggered events (via triggerServerEvent) (#3251)
Dutchman101 pushed a commit that referenced this pull request Nov 27, 2023
MTABot pushed a commit that referenced this pull request Nov 27, 2023
@Lpsd Lpsd mentioned this pull request Nov 29, 2023
1 task
Dutchman101 pushed a commit that referenced this pull request Nov 29, 2023
MTABot pushed a commit that referenced this pull request Nov 29, 2023
TracerDS pushed a commit to TracerDS/mtasa-blue that referenced this pull request Feb 2, 2024
TracerDS pushed a commit to TracerDS/mtasa-blue that referenced this pull request Feb 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants