mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-12 17:14:46 +02:00
Add helper functions and start the IPv6 /128 to /64 transition in
connect-flood and maxperip module. This so they actually take set::default-ipv6-clone-mask into account. This also changes the maxperip module to a more simple method of just freeing all entries and rebuilding the hash table on load. That's necessary since now set::default-ipv6-clone-mask can change.
This commit is contained in:
@@ -527,6 +527,9 @@ extern int checkprotoflags(Client *, int, const char *, int);
|
||||
|
||||
extern const char *inetntop(int af, const void *in, char *local_dummy, size_t the_size);
|
||||
|
||||
extern void mask_ipv6_rawip(const char *src, int prefix, char *dst);
|
||||
extern const char *get_clone_mask_ipstr(Client *client, char *buf, size_t buflen);
|
||||
|
||||
extern void delletterfromstring(char *s, char letter);
|
||||
extern void addlettertodynamicstringsorted(char **str, char letter);
|
||||
extern int sort_character_lowercase_before_uppercase(char x, char y);
|
||||
|
||||
+60
@@ -879,3 +879,63 @@ void badword_config_free(ConfigItem_badword *e)
|
||||
pcre2_code_free(e->pcre2_expr);
|
||||
safe_free(e);
|
||||
}
|
||||
|
||||
/** Mask the lower bits of an IPv6 raw address.
|
||||
*
|
||||
* Bits past 'prefix' are zeroed, leaving only the upper 'prefix' bits set
|
||||
* to whatever they were in 'src'.
|
||||
*
|
||||
* @param src 16-byte source raw IPv6 address.
|
||||
* @param prefix Prefix length in bits (0-128).
|
||||
* @param dst 16-byte destination buffer (may alias src).
|
||||
*/
|
||||
void mask_ipv6_rawip(const char *src, int prefix, char *dst)
|
||||
{
|
||||
int full_bytes = prefix / 8;
|
||||
int leftover_bits = prefix % 8;
|
||||
|
||||
if (src != dst)
|
||||
memcpy(dst, src, 16);
|
||||
|
||||
if (leftover_bits > 0 && full_bytes < 16)
|
||||
{
|
||||
unsigned char mask = (unsigned char)(0xFF << (8 - leftover_bits));
|
||||
dst[full_bytes] = (char)((unsigned char)src[full_bytes] & mask);
|
||||
full_bytes++;
|
||||
}
|
||||
|
||||
if (full_bytes < 16)
|
||||
memset(dst + full_bytes, 0, 16 - full_bytes);
|
||||
}
|
||||
|
||||
/** Get the IP address string of a client, masked according to
|
||||
* set::default-ipv6-clone-mask.
|
||||
*
|
||||
* For IPv4 clients: returns client->ip unchanged (no masking applies).
|
||||
* For IPv6 clients: returns the canonical form with bits past
|
||||
* iConf.default_ipv6_clone_mask zeroed (e.g., "2001:db8:1:2::" for /64).
|
||||
*
|
||||
* Useful for any per-host bookkeeping that should treat all addresses
|
||||
* within a /N as a single "host" — maxperip, connect-flood, reputation, etc.
|
||||
*
|
||||
* @param client The client.
|
||||
* @param buf Output buffer.
|
||||
* @param buflen Length of buf (recommended: HOSTLEN+1 or larger).
|
||||
* @return Pointer to buf on success, or NULL on failure.
|
||||
*/
|
||||
const char *get_clone_mask_ipstr(Client *client, char *buf, size_t buflen)
|
||||
{
|
||||
char masked[16];
|
||||
|
||||
if (!client || !client->ip || !buf || buflen == 0)
|
||||
return NULL;
|
||||
|
||||
if (!IsIPV6(client))
|
||||
{
|
||||
strlcpy(buf, client->ip, buflen);
|
||||
return buf;
|
||||
}
|
||||
|
||||
mask_ipv6_rawip(client->rawip, iConf.default_ipv6_clone_mask, masked);
|
||||
return inetntop(AF_INET6, masked, buf, buflen);
|
||||
}
|
||||
|
||||
@@ -173,13 +173,21 @@ uint64_t hash_throttling(const char *ip)
|
||||
|
||||
ThrottlingBucket *find_throttling_bucket(Client *client)
|
||||
{
|
||||
int hash = 0;
|
||||
int hash;
|
||||
ThrottlingBucket *p;
|
||||
hash = hash_throttling(client->ip);
|
||||
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, client->ip))
|
||||
if (!strcmp(p->ip, ip))
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -210,13 +218,20 @@ 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, client->ip);
|
||||
safe_strdup(n->ip, ip);
|
||||
n->since = TStime();
|
||||
n->count = 1;
|
||||
hash = hash_throttling(client->ip);
|
||||
hash = hash_throttling(ip);
|
||||
AddListItem(n, ThrottlingHash[hash]);
|
||||
return;
|
||||
}
|
||||
|
||||
+109
-54
@@ -51,12 +51,12 @@ int maxperip_config_test_allow(ConfigFile *cf, ConfigEntry *ce, int type, int *e
|
||||
int maxperip_config_run_allow(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr);
|
||||
void maxperip_postconf(void);
|
||||
int exceeds_maxperip(Client *client, ConfigItem_allow *aconf);
|
||||
void siphashkey_ipusers_free(ModData *m);
|
||||
void ipusershash_free_4(ModData *m);
|
||||
void ipusershash_free_6(ModData *m);
|
||||
IpUsersBucket *find_ipusers_bucket(Client *client);
|
||||
IpUsersBucket *add_ipusers_bucket(Client *client);
|
||||
void decrease_ipusers_bucket(Client *client);
|
||||
int decrease_ipusers_bucket_wrapper(Client *client);
|
||||
static void rebuild_ipusers_buckets(void);
|
||||
static void free_ipusers_buckets(void);
|
||||
int stats_maxperip(Client *client, const char *para);
|
||||
int maxperip_remote_connect(Client *client);
|
||||
const char *maxperip_allow_client(Client *client, ConfigItem_allow *aconf);
|
||||
@@ -73,18 +73,11 @@ MOD_TEST()
|
||||
MOD_INIT()
|
||||
{
|
||||
MARK_AS_OFFICIAL_MODULE(modinfo);
|
||||
LoadPersistentPointer(modinfo, siphashkey_ipusers, siphashkey_ipusers_free);
|
||||
if (!siphashkey_ipusers)
|
||||
{
|
||||
siphashkey_ipusers = safe_alloc(SIPHASH_KEY_LENGTH);
|
||||
siphash_generate_key(siphashkey_ipusers);
|
||||
}
|
||||
LoadPersistentPointer(modinfo, IpUsersHash_ipv4, ipusershash_free_4);
|
||||
if (!IpUsersHash_ipv4)
|
||||
IpUsersHash_ipv4 = safe_alloc(sizeof(IpUsersBucket *) * IPUSERS_HASH_TABLE_SIZE);
|
||||
LoadPersistentPointer(modinfo, IpUsersHash_ipv6, ipusershash_free_6);
|
||||
if (!IpUsersHash_ipv6)
|
||||
IpUsersHash_ipv6 = safe_alloc(sizeof(IpUsersBucket *) * IPUSERS_HASH_TABLE_SIZE);
|
||||
|
||||
siphashkey_ipusers = safe_alloc(SIPHASH_KEY_LENGTH);
|
||||
siphash_generate_key(siphashkey_ipusers);
|
||||
IpUsersHash_ipv4 = safe_alloc(sizeof(IpUsersBucket *) * IPUSERS_HASH_TABLE_SIZE);
|
||||
IpUsersHash_ipv6 = safe_alloc(sizeof(IpUsersBucket *) * IPUSERS_HASH_TABLE_SIZE);
|
||||
|
||||
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN_EX, 0, maxperip_config_run_allow);
|
||||
HookAdd(modinfo->handle, HOOKTYPE_FREE_USER, 0, decrease_ipusers_bucket_wrapper);
|
||||
@@ -98,14 +91,14 @@ MOD_INIT()
|
||||
MOD_LOAD()
|
||||
{
|
||||
maxperip_postconf();
|
||||
rebuild_ipusers_buckets();
|
||||
return MOD_SUCCESS;
|
||||
}
|
||||
|
||||
MOD_UNLOAD()
|
||||
{
|
||||
SavePersistentPointer(modinfo, siphashkey_ipusers);
|
||||
SavePersistentPointer(modinfo, IpUsersHash_ipv4);
|
||||
SavePersistentPointer(modinfo, IpUsersHash_ipv6);
|
||||
free_ipusers_buckets();
|
||||
safe_free(siphashkey_ipusers);
|
||||
return MOD_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -178,51 +171,48 @@ void maxperip_postconf(void)
|
||||
}
|
||||
}
|
||||
|
||||
void siphashkey_ipusers_free(ModData *m)
|
||||
{
|
||||
safe_free(siphashkey_ipusers);
|
||||
m->ptr = NULL;
|
||||
}
|
||||
|
||||
void ipusershash_free_4(ModData *m)
|
||||
{
|
||||
// FIXME: need to free every bucket in a for loop
|
||||
// and then end with this:
|
||||
safe_free(IpUsersHash_ipv4);
|
||||
m->ptr = NULL;
|
||||
}
|
||||
|
||||
void ipusershash_free_6(ModData *m)
|
||||
{
|
||||
// FIXME: need to free every bucket in a for loop
|
||||
// and then end with this:
|
||||
safe_free(IpUsersHash_ipv6);
|
||||
m->ptr = NULL;
|
||||
}
|
||||
|
||||
uint64_t hash_ipusers(Client *client)
|
||||
/** Build the rawip used to identify this client's ipusers bucket.
|
||||
*
|
||||
* For IPv4: copies the 4 raw bytes of client->rawip.
|
||||
* For IPv6: copies and masks client->rawip according to
|
||||
* iConf.default_ipv6_clone_mask, so all addresses within the
|
||||
* same /N share one bucket.
|
||||
*
|
||||
* The 'rawip' buffer must be at least 16 bytes (only first 4 used for IPv4).
|
||||
*/
|
||||
static void make_ipusers_rawip(Client *client, char *rawip)
|
||||
{
|
||||
if (IsIPV6(client))
|
||||
return siphash_raw(client->rawip, 16, siphashkey_ipusers) % IPUSERS_HASH_TABLE_SIZE;
|
||||
mask_ipv6_rawip(client->rawip, iConf.default_ipv6_clone_mask, rawip);
|
||||
else
|
||||
return siphash_raw(client->rawip, 4, siphashkey_ipusers) % IPUSERS_HASH_TABLE_SIZE;
|
||||
memcpy(rawip, client->rawip, 4);
|
||||
}
|
||||
|
||||
uint64_t hash_ipusers(Client *client, const char *rawip)
|
||||
{
|
||||
if (IsIPV6(client))
|
||||
return siphash_raw(rawip, 16, siphashkey_ipusers) % IPUSERS_HASH_TABLE_SIZE;
|
||||
else
|
||||
return siphash_raw(rawip, 4, siphashkey_ipusers) % IPUSERS_HASH_TABLE_SIZE;
|
||||
}
|
||||
|
||||
IpUsersBucket *find_ipusers_bucket(Client *client)
|
||||
{
|
||||
int hash = 0;
|
||||
int hash;
|
||||
IpUsersBucket *p;
|
||||
char rawip[16];
|
||||
|
||||
hash = hash_ipusers(client);
|
||||
make_ipusers_rawip(client, rawip);
|
||||
hash = hash_ipusers(client, rawip);
|
||||
|
||||
if (IsIPV6(client))
|
||||
{
|
||||
for (p = IpUsersHash_ipv6[hash]; p; p = p->next)
|
||||
if (memcmp(p->rawip, client->rawip, 16) == 0)
|
||||
if (memcmp(p->rawip, rawip, 16) == 0)
|
||||
return p;
|
||||
} else {
|
||||
for (p = IpUsersHash_ipv4[hash]; p; p = p->next)
|
||||
if (memcmp(p->rawip, client->rawip, 4) == 0)
|
||||
if (memcmp(p->rawip, rawip, 4) == 0)
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -240,16 +230,18 @@ IpUsersBucket *add_ipusers_bucket(Client *client)
|
||||
{
|
||||
int hash;
|
||||
IpUsersBucket *n;
|
||||
char rawip[16];
|
||||
|
||||
hash = hash_ipusers(client);
|
||||
make_ipusers_rawip(client, rawip);
|
||||
hash = hash_ipusers(client, rawip);
|
||||
|
||||
n = safe_alloc(sizeof(IpUsersBucket));
|
||||
if (IsIPV6(client))
|
||||
{
|
||||
memcpy(n->rawip, client->rawip, 16);
|
||||
memcpy(n->rawip, rawip, 16);
|
||||
AddListItem(n, IpUsersHash_ipv6[hash]);
|
||||
} else {
|
||||
memcpy(n->rawip, client->rawip, 4);
|
||||
memcpy(n->rawip, rawip, 4);
|
||||
AddListItem(n, IpUsersHash_ipv4[hash]);
|
||||
}
|
||||
return n;
|
||||
@@ -257,24 +249,26 @@ IpUsersBucket *add_ipusers_bucket(Client *client)
|
||||
|
||||
void decrease_ipusers_bucket(Client *client)
|
||||
{
|
||||
int hash = 0;
|
||||
int hash;
|
||||
IpUsersBucket *p;
|
||||
char rawip[16];
|
||||
|
||||
if (!(client->flags & CLIENT_FLAG_IPUSERS_BUMPED))
|
||||
return; /* nothing to do */
|
||||
|
||||
client->flags &= ~CLIENT_FLAG_IPUSERS_BUMPED;
|
||||
|
||||
hash = hash_ipusers(client);
|
||||
make_ipusers_rawip(client, rawip);
|
||||
hash = hash_ipusers(client, rawip);
|
||||
|
||||
if (IsIPV6(client))
|
||||
{
|
||||
for (p = IpUsersHash_ipv6[hash]; p; p = p->next)
|
||||
if (memcmp(p->rawip, client->rawip, 16) == 0)
|
||||
if (memcmp(p->rawip, rawip, 16) == 0)
|
||||
break;
|
||||
} else {
|
||||
for (p = IpUsersHash_ipv4[hash]; p; p = p->next)
|
||||
if (memcmp(p->rawip, client->rawip, 4) == 0)
|
||||
if (memcmp(p->rawip, rawip, 4) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -299,6 +293,67 @@ void decrease_ipusers_bucket(Client *client)
|
||||
}
|
||||
}
|
||||
|
||||
/* Restore the buckets by walking current clients with the bumped flag.
|
||||
* Cost is negligible (a few tens of milliseconds even for ~10k clients).
|
||||
*/
|
||||
static void rebuild_ipusers_buckets(void)
|
||||
{
|
||||
Client *client;
|
||||
IpUsersBucket *bucket;
|
||||
|
||||
list_for_each_entry(client, &client_list, client_node)
|
||||
{
|
||||
if (!(client->flags & CLIENT_FLAG_IPUSERS_BUMPED))
|
||||
continue;
|
||||
if (!client->ip)
|
||||
continue; /* defensive */
|
||||
bucket = find_ipusers_bucket(client);
|
||||
if (!bucket)
|
||||
bucket = add_ipusers_bucket(client);
|
||||
bucket->global_clients++;
|
||||
if (MyConnect(client))
|
||||
bucket->local_clients++;
|
||||
}
|
||||
list_for_each_entry(client, &unknown_list, lclient_node)
|
||||
{
|
||||
if (!(client->flags & CLIENT_FLAG_IPUSERS_BUMPED))
|
||||
continue;
|
||||
if (!client->ip)
|
||||
continue;
|
||||
bucket = find_ipusers_bucket(client);
|
||||
if (!bucket)
|
||||
bucket = add_ipusers_bucket(client);
|
||||
bucket->global_clients++;
|
||||
if (MyConnect(client))
|
||||
bucket->local_clients++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Free every bucket in both hash tables, then free the tables themselves. */
|
||||
static void free_ipusers_buckets(void)
|
||||
{
|
||||
int i;
|
||||
IpUsersBucket *p, *next;
|
||||
|
||||
for (i = 0; i < IPUSERS_HASH_TABLE_SIZE; i++)
|
||||
{
|
||||
for (p = IpUsersHash_ipv4[i]; p; p = next)
|
||||
{
|
||||
next = p->next;
|
||||
safe_free(p);
|
||||
}
|
||||
IpUsersHash_ipv4[i] = NULL;
|
||||
for (p = IpUsersHash_ipv6[i]; p; p = next)
|
||||
{
|
||||
next = p->next;
|
||||
safe_free(p);
|
||||
}
|
||||
IpUsersHash_ipv6[i] = NULL;
|
||||
}
|
||||
safe_free(IpUsersHash_ipv4);
|
||||
safe_free(IpUsersHash_ipv6);
|
||||
}
|
||||
|
||||
int stats_maxperip(Client *client, const char *para)
|
||||
{
|
||||
int i;
|
||||
|
||||
Reference in New Issue
Block a user