├── .gitignore ├── premake5.lua ├── README.md └── src └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | projects/ 3 | garrysmod_common/ -------------------------------------------------------------------------------- /premake5.lua: -------------------------------------------------------------------------------- 1 | PROJECT_GENERATOR_VERSION = 3 2 | 3 | newoption({ 4 | trigger = "gmcommon", 5 | description = "Sets the path to the garrysmod_common (https://github.com/danielga/garrysmod_common) directory", 6 | value = "path to garrysmod_common directory" 7 | }) 8 | 9 | local gmcommon = assert(_OPTIONS.gmcommon or os.getenv("GARRYSMOD_COMMON"), 10 | "you didn't provide a path to your garrysmod_common (https://github.com/danielga/garrysmod_common) directory") 11 | include(gmcommon) 12 | 13 | CreateWorkspace({name = "netlimiter", abi_compatible = true}) 14 | CreateProject({serverside = true}) 15 | IncludeLuaShared() 16 | IncludeHelpersExtended() 17 | IncludeSDKCommon() 18 | IncludeSDKTier0() 19 | IncludeSDKTier1() 20 | IncludeSDKLZMA() 21 | IncludeScanning() 22 | IncludeDetouring() 23 | files({"src/*.cpp", "src/*.hpp"}) 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## NetLimiter for Garry's Mod servers 2 | 3 | This module is an implementation of the net_chan_limit_msec convar that is available on CSGO and TF2 servers.
4 | This module allows you to limit the amount of processing time a player can use for networking effectively killing all flooding exploits. It works by detouring the ProcessMessages function that handles all networking.
5 | This module is also designed to be as simplified as possible to insure that it is optimized. 6 | 7 | ## Usage. 8 | 9 | Place the DLL in lua/bin.
10 | Create an file in lua/autorun/server that runs ``require("netlimiter")`` and define ``net_chan_limit_msec 100`` in your server.cfg. You can change the limit to what suites your server best. 11 | 12 | ## Compilation 13 | To compile this project you will need [garrysmod_common][1]. 14 | 15 | 16 | ## Credits other than myself 17 | [Asriel][2]: Helped me get the detour working and with implementing the ratelimiting itself. Was good fun creating this module.
18 | [Daniel][3]: He is the creator of garrysmod_common which makes it easier for developers to create modules for Garry's Mod and also created a module called sourcenet(My favourite module) that gave a good insight into source engine networking. 19 | 20 | [1]: https://github.com/danielga/garrysmod_common 21 | [2]: https://github.com/A5R13L 22 | [3]: https://github.com/danielga 23 | 24 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // Define CNetChan in terms of INetChannel 15 | class CNetChan : public INetChannel 16 | { 17 | }; 18 | 19 | namespace global 20 | { 21 | // Create a global lua state 22 | GarrysMod::Lua::ILuaInterface *lua = nullptr; 23 | 24 | // Create a global pointer to the original function 25 | FunctionPointers::CNetChan_ProcessMessages_t ProcessMessages_original = nullptr; 26 | 27 | // Create a pair of a uint64 and a chrono::duration 28 | using TimePair = std::pair>; 29 | Detouring::Hook ProcessMessagesHook; 30 | std::map ProcessingTimes; 31 | 32 | bool ProcessMessages_Hook(CNetChan *Channel, bf_read &Buffer) 33 | { 34 | // Get the original function 35 | static FunctionPointers::CNetChan_ProcessMessages_t Trampoline = ProcessMessagesHook.GetTrampoline(); 36 | static ConVar *net_chan_limit_msec; 37 | 38 | // Get the net_chan_limit_msec convar 39 | if (!net_chan_limit_msec) 40 | net_chan_limit_msec = InterfacePointers::Cvar()->FindVar("net_chan_limit_msec"); 41 | 42 | // If the convar is not set or is set to 0, call the original function 43 | if (!net_chan_limit_msec || net_chan_limit_msec->GetInt() == 0) 44 | return Trampoline(Channel, Buffer); 45 | 46 | // Get the processing time for the client and call the original function 47 | std::chrono::time_point Start = std::chrono::system_clock::now(); 48 | bool Return = Trampoline(Channel, Buffer); 49 | std::chrono::time_point End = std::chrono::system_clock::now(); 50 | const double MS = ((End.time_since_epoch() - Start.time_since_epoch()) / 1000.0f / 1000.0f).count(); 51 | 52 | // Create a new entry if the client is not in the map 53 | if (ProcessingTimes.find(Channel) == ProcessingTimes.end()) 54 | ProcessingTimes[Channel] = std::make_pair>(0, std::chrono::system_clock::time_point::duration(0)); 55 | 56 | // Reset the processing time if it has been more than a milisecond since the last reset 57 | TimePair &Data = ProcessingTimes[Channel]; 58 | 59 | // Check if the time has been more than a milisecond since the last reset 60 | if (Data.second + std::chrono::seconds(1) < End.time_since_epoch()) 61 | { 62 | Data.first = 0; 63 | Data.second = End.time_since_epoch(); 64 | } 65 | 66 | // Add the processing time to the total 67 | Data.first += MS; 68 | 69 | // Check if the client has exceeded the limit 70 | if (Data.first >= net_chan_limit_msec->GetInt()) 71 | { 72 | // Shutdown the client 73 | Data.first = 0; 74 | Data.second = End.time_since_epoch(); 75 | Channel->Shutdown("Exceeded net processing time."); 76 | 77 | return false; 78 | } 79 | 80 | // Return the original value 81 | return Return; 82 | } 83 | 84 | Detouring::Hook::Target target; 85 | 86 | static void Initialize(GarrysMod::Lua::ILuaBase *LUA) 87 | { 88 | global::lua = reinterpret_cast(LUA); 89 | 90 | // Register the convar using the global lua state 91 | global::lua->CreateConVar("net_chan_limit_msec", "0", "Netchannel processing is limited to so many milliseconds, abort connection if exceeding budget.", FCVAR_ARCHIVE | FCVAR_GAMEDLL); 92 | 93 | // Get a pointer to the original function 94 | global::ProcessMessages_original = FunctionPointers::CNetChan_ProcessMessages(); 95 | 96 | // Check if the function exists 97 | if (ProcessMessages_original == nullptr) 98 | { 99 | LUA->ThrowError("failed to retrieve CNetChan::ProcessMessages"); 100 | return; 101 | } 102 | 103 | // Create a target for the hook 104 | global::target = Detouring::Hook::Target((void *)ProcessMessages_original); 105 | 106 | // Check if the target is valid 107 | if (!global::target.IsValid()) 108 | { 109 | LUA->ThrowError("Failed to create target"); 110 | return; 111 | } 112 | 113 | // Create the hook 114 | if (!global::ProcessMessagesHook.Create(global::target, reinterpret_cast(&global::ProcessMessages_Hook))) 115 | { 116 | LUA->ThrowError("Failed to create hook"); 117 | return; 118 | } 119 | 120 | // Enable the hook 121 | global::ProcessMessagesHook.Enable(); 122 | } 123 | 124 | static void deinitialize() 125 | { 126 | // Disable the hook 127 | global::ProcessMessagesHook.Destroy(); 128 | } 129 | 130 | } 131 | 132 | GMOD_MODULE_OPEN() 133 | { 134 | // Initialize the global lua state 135 | global::Initialize(LUA); 136 | return 0; 137 | } 138 | 139 | GMOD_MODULE_CLOSE() 140 | { 141 | // Deinitialize and destroy the hook 142 | global::deinitialize(); 143 | return 0; 144 | } --------------------------------------------------------------------------------