1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-06 11:13:12 +02:00
Files
unrealircd/src/modules/connect-flood.c
T
Bram Matthys 548d66d26c Reformat all source code, using 'make format' (clang-format 21.1.8)
This reformats entire UnrealIRCd source code, according to
.clang-format which was previously commited.

This is reproducable using clang-format version 21.1.8.

These are only visual changes, they have no effect on code.

This commit will be added to .git-blame-ignore-revs so 'git blame'
and the similar blame view on GitHub will ignore this commit.
2026-07-05 12:11:51 +02:00

261 lines
6.2 KiB
C

/*
* Connection throttling (set::anti-flood::connect-flood)
* (C) Copyright 2022- Bram Matthys and the UnrealIRCd team.
* License: GPLv2 or later
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER = {
"connect-flood",
"6.0.0",
"set::anti-flood::connect-flood",
"UnrealIRCd Team",
"unrealircd-6",
};
/* Defines */
#define THROTTLING_HASH_TABLE_SIZE 8192
/* Structs */
typedef struct ThrottlingBucket ThrottlingBucket;
struct ThrottlingBucket {
ThrottlingBucket *prev, *next;
char *ip;
time_t since;
char count;
};
/* Variables */
char *siphashkey_throttling = NULL;
ThrottlingBucket **ThrottlingHash = NULL;
/* Forward declaration */
int connect_flood_accept(Client *client);
int connect_flood_dns_finished(Client *client);
int connect_flood_ip_change(Client *client, const char *oldip);
void siphashkey_throttling_free(ModData *m);
void throttlinghash_free(ModData *m);
void add_throttling_timeout_timer(ModuleInfo *modinfo);
int throttle_can_connect(Client *client);
void add_throttling_bucket(Client *client);
EVENT(throttling_check_expire);
MOD_INIT()
{
MARK_AS_OFFICIAL_MODULE(modinfo);
LoadPersistentPointer(modinfo, siphashkey_throttling, siphashkey_throttling_free);
if (!siphashkey_throttling)
{
siphashkey_throttling = safe_alloc(SIPHASH_KEY_LENGTH);
siphash_generate_key(siphashkey_throttling);
}
LoadPersistentPointer(modinfo, ThrottlingHash, throttlinghash_free);
if (!ThrottlingHash)
{
ThrottlingHash = safe_alloc(sizeof(ThrottlingBucket *) * THROTTLING_HASH_TABLE_SIZE);
}
HookAdd(modinfo->handle, HOOKTYPE_ACCEPT, -3000, connect_flood_accept);
HookAdd(modinfo->handle, HOOKTYPE_DNS_FINISHED, -3000, connect_flood_dns_finished);
HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, -3000, connect_flood_ip_change);
return MOD_SUCCESS;
}
MOD_LOAD()
{
add_throttling_timeout_timer(modinfo);
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
SavePersistentPointer(modinfo, siphashkey_throttling);
SavePersistentPointer(modinfo, ThrottlingHash);
return MOD_SUCCESS;
}
void siphashkey_throttling_free(ModData *m)
{
safe_free(siphashkey_throttling);
m->ptr = NULL;
}
void throttlinghash_free(ModData *m)
{
// FIXME: need to free every throttling bucket in a for loop
// and then end with this:
safe_free(ThrottlingHash);
m->ptr = NULL;
}
int connect_flood_throttle(Client *client, int exitflags)
{
int val;
char zlinebuf[512];
if (!(val = throttle_can_connect(client)))
{
ircsnprintf(zlinebuf, sizeof(zlinebuf),
"Throttled: Reconnecting too fast - "
"Email %s for more information.",
KLINE_ADDRESS);
/* There are two reasons why we can't use exit_client() here:
* 1) Because the HOOKTYPE_IP_CHANGE call may be too deep.
* Eg: read_packet -> webserver_packet_in ->
* webserver_handle_request_header -> webserver_handle_request ->
* RunHook().... and then returning without touching anything
* after an exit_client() would not be feasible.
* 2) Because in HOOKTYPE_ACCEPT we always need to use dead_socket
* if we want to print a friendly message to TLS users.
*/
dead_socket(client, zlinebuf);
return HOOK_DENY;
} else if (val == 1)
add_throttling_bucket(client);
return 0;
}
int connect_flood_accept(Client *client)
{
if (!quick_close)
return 0; /* defer to connect_flood_dns_finished so DNS on except ban works */
if (client->local->listener->options & LISTENER_NO_CHECK_CONNECT_FLOOD)
return 0;
client->flags |= CLIENT_FLAG_CONNECT_FLOOD_CHECKED;
return connect_flood_throttle(client, NO_EXIT_CLIENT);
}
int connect_flood_dns_finished(Client *client)
{
if (client->flags & CLIENT_FLAG_CONNECT_FLOOD_CHECKED)
return 0;
if (client->local->listener->options & LISTENER_NO_CHECK_CONNECT_FLOOD)
return 0;
return connect_flood_throttle(client, NO_EXIT_CLIENT);
}
int connect_flood_ip_change(Client *client, const char *oldip)
{
return connect_flood_throttle(client, 0);
}
void add_throttling_timeout_timer(ModuleInfo *modinfo)
{
long v;
if (!THROTTLING_PERIOD)
{
v = 120 * 1000;
} else
{
v = (THROTTLING_PERIOD * 1000) / 2;
if (v > 5000)
v = 5000; /* run at least every 5s */
if (v < 1000)
v = 1000; /* run at max once every 1s */
}
EventAdd(modinfo->handle, "throttling_check_expire", throttling_check_expire, NULL, v, 0);
}
uint64_t hash_throttling(const char *ip)
{
return siphash(ip, siphashkey_throttling) % THROTTLING_HASH_TABLE_SIZE;
}
ThrottlingBucket *find_throttling_bucket(Client *client)
{
int hash;
ThrottlingBucket *p;
char ip[HOSTLEN + 1];
/* Apply set::default-ipv6-clone-mask: bucket is keyed by the network
* portion of the IP, so all addresses in the same /64 share one bucket.
*/
if (!get_clone_mask_ipstr(client, ip, sizeof(ip)))
return NULL;
hash = hash_throttling(ip);
for (p = ThrottlingHash[hash]; p; p = p->next)
{
if (!strcmp(p->ip, ip))
return p;
}
return NULL;
}
EVENT(throttling_check_expire)
{
ThrottlingBucket *n, *n_next;
int i;
for (i = 0; i < THROTTLING_HASH_TABLE_SIZE; i++)
{
for (n = ThrottlingHash[i]; n; n = n_next)
{
n_next = n->next;
if ((TStime() - n->since) > (THROTTLING_PERIOD ? THROTTLING_PERIOD : 15))
{
DelListItem(n, ThrottlingHash[i]);
safe_free(n->ip);
safe_free(n);
}
}
}
}
void add_throttling_bucket(Client *client)
{
int hash;
ThrottlingBucket *n;
char ip[HOSTLEN + 1];
/* Apply set::default-ipv6-clone-mask: bucket is keyed by the network
* portion of the IP, so all addresses in the same /64 share one bucket.
*/
if (!get_clone_mask_ipstr(client, ip, sizeof(ip)))
return;
n = safe_alloc(sizeof(ThrottlingBucket));
n->next = n->prev = NULL;
safe_strdup(n->ip, ip);
n->since = TStime();
n->count = 1;
hash = hash_throttling(ip);
AddListItem(n, ThrottlingHash[hash]);
return;
}
/** Checks whether the user is connect-flooding.
* @retval 0 Denied, throttled.
* @retval 1 Allowed, but known in the list.
* @retval 2 Allowed, not in list or is an exception.
* @see add_connection()
*/
int throttle_can_connect(Client *client)
{
ThrottlingBucket *b;
if (!THROTTLING_PERIOD || !THROTTLING_COUNT)
return 2;
if (!(b = find_throttling_bucket(client)))
return 1;
else
{
if (find_tkl_exception(TKL_CONNECT_FLOOD, client))
return 2;
if (b->count + 1 > (THROTTLING_COUNT ? THROTTLING_COUNT : 3))
return 0;
b->count++;
return 2;
}
}