From eb8924c8335a28fbce4369c0728eccee58d1fc07 Mon Sep 17 00:00:00 2001 From: bones_was_here Date: Thu, 24 Nov 2022 21:43:40 +1000 Subject: [PATCH] Improve SVQC command flood control Changes to the same logic used by chat flood control which is more progressive. The previous command flood control was only triggered if a client managed to send more than 8 commands within 1 second, and then it reset immediately. Prints an informative message when flood control blocks a command. Previously this was silent. Special cases the client's initial connect commands, as these would otherwise trigger the new flood logic with the current default settings. Exempts chat commands from command flood control, as these have their own flood control. Simplifies the code for max perf. --- qcsrc/server/chat.qc | 4 ++-- qcsrc/server/client.qh | 1 - qcsrc/server/command/cmd.qc | 40 +++++++++++++++++-------------------- qcsrc/server/command/cmd.qh | 1 - 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/qcsrc/server/chat.qc b/qcsrc/server/chat.qc index a73de2a1f..41af1de57 100644 --- a/qcsrc/server/chat.qc +++ b/qcsrc/server/chat.qc @@ -208,7 +208,7 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc { if(autocvar_g_chat_flood_notify_flooder) { - sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n"); + sourcemsgstr = strcat(msgstr, "\n^3CHAT FLOOD CONTROL: ^7message too long, trimmed\n"); sourcecmsgstr = ""; } else @@ -247,7 +247,7 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc { if (autocvar_g_chat_flood_notify_flooder) { - sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n")); + sprint(source, strcat("^3CHAT FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n")); ret = 0; } else diff --git a/qcsrc/server/client.qh b/qcsrc/server/client.qh index eebf016f7..33b9d4511 100644 --- a/qcsrc/server/client.qh +++ b/qcsrc/server/client.qh @@ -185,7 +185,6 @@ CLASS(Client, Object) ATTRIB(Client, specialcommand_pos, int, this.specialcommand_pos); ATTRIB(Client, hitplotfh, int, this.hitplotfh); ATTRIB(Client, clientdata, entity, this.clientdata); - ATTRIB(Client, cmd_floodcount, int, this.cmd_floodcount); ATTRIB(Client, cmd_floodtime, float, this.cmd_floodtime); ATTRIB(Client, wasplayer, bool, this.wasplayer); ATTRIB(Client, weaponorder_byimpulse, string, this.weaponorder_byimpulse); diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index 50b71957b..0ee108c7c 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -40,26 +40,6 @@ // Last updated: December 28th, 2011 // ========================================================= -bool SV_ParseClientCommand_floodcheck(entity this) -{ - entity store = IS_CLIENT(this) ? CS(this) : this; // unfortunately, we need to store these on the client initially - - if (!timeout_status) // not while paused - { - if (time <= (store.cmd_floodtime + autocvar_sv_clientcommand_antispam_time)) - { - store.cmd_floodcount += 1; - if (store.cmd_floodcount > autocvar_sv_clientcommand_antispam_count) return false; // too much spam, halt - } - else - { - store.cmd_floodtime = time; - store.cmd_floodcount = 1; - } - } - return true; // continue, as we're not flooding yet -} - // ======================= // Command Sub-Functions @@ -889,6 +869,7 @@ void SV_ParseClientCommand(entity this, string command) case "prespawn": break; // handled by engine in host_cmd.c case "sentcvar": break; // handled by server in this file case "spawn": break; // handled by engine in host_cmd.c + case "say": case "say_team": case "tell": break; // chat has its own flood control in chat.qc case "color": case "topcolor": case "bottomcolor": // handled by engine in host_cmd.c if(!IS_CLIENT(this)) // on connection { @@ -900,9 +881,24 @@ void SV_ParseClientCommand(entity this, string command) break; case "c2s": Net_ClientCommand(this, command); return; // handled by net.qh + // on connection, client sends all of these + case "name": case "rate": case "rate_burstsize": case "playermodel": case "playerskin": case "clientversion": + if(!IS_CLIENT(this)) break; + // else fall through to default: flood control default: - if (SV_ParseClientCommand_floodcheck(this)) break; // "true": continue, as we're not flooding yet - else return; // "false": not allowed to continue, halt // print("^1ERROR: ^7ANTISPAM CAUGHT: ", command, ".\n"); + if (!timeout_status) // not while paused + { + entity store = IS_CLIENT(this) ? CS(this) : this; // unfortunately, we need to store these on the client initially + // this is basically the same as the chat flood control + if (time < store.cmd_floodtime) + { + sprint(this, strcat("^3CMD FLOOD CONTROL: wait ^1", ftos(store.cmd_floodtime - time), "^3 seconds, command was: ", command, "\n")); + return; // too much spam, halt + } + else + store.cmd_floodtime = max(time - autocvar_sv_clientcommand_antispam_count * autocvar_sv_clientcommand_antispam_time, store.cmd_floodtime) + autocvar_sv_clientcommand_antispam_time; + } + break; // continue, as we're not flooding yet } /* NOTE: should this be disabled? It can be spammy perhaps, but hopefully it's okay for now */ diff --git a/qcsrc/server/command/cmd.qh b/qcsrc/server/command/cmd.qh index 802afc8bd..bb97f0d0b 100644 --- a/qcsrc/server/command/cmd.qh +++ b/qcsrc/server/command/cmd.qh @@ -4,7 +4,6 @@ float autocvar_sv_clientcommand_antispam_time; int autocvar_sv_clientcommand_antispam_count; .float cmd_floodtime; -.float cmd_floodcount; string MapVote_Suggest(entity this, string m); -- 2.39.2