mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-07-05 06:33:14 +02:00
602 lines
16 KiB
C
602 lines
16 KiB
C
/*
|
|
* Unreal Internet Relay Chat Daemon, src/modules/maxperip.c
|
|
* (C) 2025 Bram Matthys and the UnrealIRCd Team
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 1, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include "unrealircd.h"
|
|
|
|
ModuleHeader MOD_HEADER
|
|
= {
|
|
"maxperip",
|
|
"2.0.0",
|
|
"Limit user connections based on ip address",
|
|
"UnrealIRCd Team",
|
|
"unrealircd-6",
|
|
};
|
|
|
|
/* Defines and macros */
|
|
#define IPUSERS_HASH_TABLE_SIZE 8192
|
|
|
|
/* Structs */
|
|
typedef struct IpUsersBucket IpUsersBucket;
|
|
struct IpUsersBucket
|
|
{
|
|
IpUsersBucket *prev, *next;
|
|
char rawip[16];
|
|
int local_clients;
|
|
int global_clients;
|
|
};
|
|
|
|
/* Variables */
|
|
IpUsersBucket **IpUsersHash_ipv4 = NULL;
|
|
IpUsersBucket **IpUsersHash_ipv6 = NULL;
|
|
char *siphashkey_ipusers = NULL;
|
|
|
|
/** set::known-cloud-services (enabled by default) */
|
|
static int known_cloud_services = 1; /* default: enabled */
|
|
|
|
/** IRCCloud gateway CIDRs.
|
|
* See https://www.irccloud.com/networks and https://www.irccloud.com/static/hosts.json
|
|
*/
|
|
static const char *irccloud_cidrs[] = {
|
|
"5.254.36.56/29",
|
|
"5.254.36.104/29",
|
|
"2a03:5180:f::/62",
|
|
"2a03:5180:f:4::/63",
|
|
"2a03:5180:f:6::/64",
|
|
NULL
|
|
};
|
|
|
|
/* Forward declarations */
|
|
int maxperip_config_test_allow(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
|
|
int maxperip_config_run_allow(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr);
|
|
int maxperip_config_test_set(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
|
|
int maxperip_config_run_set(ConfigFile *cf, ConfigEntry *ce, int type);
|
|
void maxperip_postconf(void);
|
|
int exceeds_maxperip(Client *client, ConfigItem_allow *aconf);
|
|
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);
|
|
static void add_known_cloud_services_exempts(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);
|
|
int _get_connections_from_ip(Client *client);
|
|
|
|
MOD_TEST()
|
|
{
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, maxperip_config_test_allow);
|
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, maxperip_config_test_set);
|
|
EfunctionAdd(modinfo->handle, EFUNC_GET_CONNECTIONS_FROM_IP, _get_connections_from_ip);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_INIT()
|
|
{
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
|
|
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);
|
|
|
|
known_cloud_services = 1; /* reset to default before CONFIGRUN may change it */
|
|
|
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN_EX, 0, maxperip_config_run_allow);
|
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, maxperip_config_run_set);
|
|
HookAdd(modinfo->handle, HOOKTYPE_FREE_USER, 0, decrease_ipusers_bucket_wrapper);
|
|
HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, stats_maxperip);
|
|
HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, maxperip_remote_connect);
|
|
HookAddConstString(modinfo->handle, HOOKTYPE_ALLOW_CLIENT, 0, maxperip_allow_client);
|
|
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_LOAD()
|
|
{
|
|
maxperip_postconf();
|
|
rebuild_ipusers_buckets();
|
|
add_known_cloud_services_exempts();
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_UNLOAD()
|
|
{
|
|
free_ipusers_buckets();
|
|
safe_free(siphashkey_ipusers);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
int maxperip_config_test_allow(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
|
|
{
|
|
int errors = 0;
|
|
int ext = 0;
|
|
|
|
if ((type != CONFIG_ALLOW_BLOCK) || !ce || !ce->name)
|
|
return 0;
|
|
|
|
if (!strcmp(ce->name, "maxperip") || !strcmp(ce->name, "global-maxperip"))
|
|
{
|
|
if (!ce->value)
|
|
{
|
|
config_error("%s:%i: missing parameter", ce->file->filename, ce->line_number);
|
|
errors++;
|
|
} else {
|
|
int v = atoi(ce->value);
|
|
if ((v <= 0) || (v > 1000000))
|
|
{
|
|
config_error("%s:%i: allow::%s with illegal value (must be 1-1000000)",
|
|
ce->file->filename, ce->line_number, ce->name);
|
|
errors++;
|
|
}
|
|
}
|
|
} else {
|
|
return 0; /* Unknown option for us */
|
|
}
|
|
|
|
*errs = errors;
|
|
return errors ? -1 : 1;
|
|
}
|
|
|
|
int maxperip_config_run_allow(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr)
|
|
{
|
|
ConfigEntry *cep, *cepp;
|
|
ConfigItem_allow *allow = (ConfigItem_allow *)ptr;
|
|
|
|
if ((type != CONFIG_ALLOW_BLOCK) || !ce || !ce->name)
|
|
return 0;
|
|
|
|
if (!strcmp(ce->name, "maxperip"))
|
|
{
|
|
allow->maxperip = atoi(ce->value);
|
|
} else
|
|
if (!strcmp(ce->name, "global-maxperip"))
|
|
{
|
|
allow->global_maxperip = atoi(ce->value);
|
|
} else
|
|
{
|
|
return 0; /* Unknown option for us */
|
|
}
|
|
|
|
return 1; /* Handled */
|
|
}
|
|
|
|
void maxperip_postconf(void)
|
|
{
|
|
ConfigItem_allow *allow;
|
|
for (allow = conf_allow; allow; allow = allow->next)
|
|
{
|
|
/* Default: global-maxperip = maxperip+1 */
|
|
if (allow->global_maxperip == 0)
|
|
allow->global_maxperip = allow->maxperip+1;
|
|
|
|
/* global-maxperip < maxperip makes no sense */
|
|
if (allow->global_maxperip < allow->maxperip)
|
|
allow->global_maxperip = allow->maxperip;
|
|
}
|
|
}
|
|
|
|
/** 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))
|
|
mask_ipv6_rawip(client->rawip, iConf.default_ipv6_clone_mask, rawip);
|
|
else
|
|
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;
|
|
IpUsersBucket *p;
|
|
char rawip[16];
|
|
|
|
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, rawip, 16) == 0)
|
|
return p;
|
|
} else {
|
|
for (p = IpUsersHash_ipv4[hash]; p; p = p->next)
|
|
if (memcmp(p->rawip, rawip, 4) == 0)
|
|
return p;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* (wrapper needed because hook has return type 'int' and function is 'void' */
|
|
int decrease_ipusers_bucket_wrapper(Client *client)
|
|
{
|
|
decrease_ipusers_bucket(client);
|
|
return 0;
|
|
}
|
|
|
|
IpUsersBucket *add_ipusers_bucket(Client *client)
|
|
{
|
|
int hash;
|
|
IpUsersBucket *n;
|
|
char rawip[16];
|
|
|
|
make_ipusers_rawip(client, rawip);
|
|
hash = hash_ipusers(client, rawip);
|
|
|
|
n = safe_alloc(sizeof(IpUsersBucket));
|
|
if (IsIPV6(client))
|
|
{
|
|
memcpy(n->rawip, rawip, 16);
|
|
AddListItem(n, IpUsersHash_ipv6[hash]);
|
|
} else {
|
|
memcpy(n->rawip, rawip, 4);
|
|
AddListItem(n, IpUsersHash_ipv4[hash]);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
void decrease_ipusers_bucket(Client *client)
|
|
{
|
|
int hash;
|
|
IpUsersBucket *p;
|
|
char rawip[16];
|
|
|
|
if (!(client->flags & CLIENT_FLAG_IPUSERS_BUMPED))
|
|
return; /* nothing to do */
|
|
|
|
client->flags &= ~CLIENT_FLAG_IPUSERS_BUMPED;
|
|
|
|
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, rawip, 16) == 0)
|
|
break;
|
|
} else {
|
|
for (p = IpUsersHash_ipv4[hash]; p; p = p->next)
|
|
if (memcmp(p->rawip, rawip, 4) == 0)
|
|
break;
|
|
}
|
|
|
|
if (!p)
|
|
{
|
|
unreal_log(ULOG_INFO, "user", "BUG_DECREASE_IPUSERS_BUCKET", client,
|
|
"[BUG] decrease_ipusers_bucket() called but bucket is gone for client $client.details");
|
|
return;
|
|
}
|
|
|
|
p->global_clients--;
|
|
if (MyConnect(client))
|
|
p->local_clients--;
|
|
|
|
if ((p->global_clients == 0) && (p->local_clients == 0))
|
|
{
|
|
if (IsIPV6(client))
|
|
DelListItem(p, IpUsersHash_ipv6[hash]);
|
|
else
|
|
DelListItem(p, IpUsersHash_ipv4[hash]);
|
|
safe_free(p);
|
|
}
|
|
}
|
|
|
|
/* 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 maxperip_config_test_set(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
|
|
{
|
|
int errors = 0;
|
|
|
|
if (type != CONFIG_SET)
|
|
return 0;
|
|
|
|
if (!strcmp(ce->name, "known-cloud-services"))
|
|
{
|
|
if (!ce->value)
|
|
{
|
|
config_error("%s:%i: set::known-cloud-services: no value specified",
|
|
ce->file->filename, ce->line_number);
|
|
errors++;
|
|
}
|
|
*errs = errors;
|
|
return errors ? -1 : 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int maxperip_config_run_set(ConfigFile *cf, ConfigEntry *ce, int type)
|
|
{
|
|
if (type != CONFIG_SET)
|
|
return 0;
|
|
|
|
if (!strcmp(ce->name, "known-cloud-services"))
|
|
{
|
|
known_cloud_services = config_checkval(ce->value, CFG_YESNO);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Install default maxperip/connect-flood exception for IRC platforms
|
|
* that are so big that they are known to trip default maxperip restrictions
|
|
* (per IPv4 IP or per IPv6 /64: 3 local users, 4 network-wide users)
|
|
* on dozens of networks and that publish a stable list of IP ranges.
|
|
* Currently only IRCCloud qualifies for this.
|
|
* IRCCloud is in example conf since May 2023 (commit 82dbc4a29716) as:
|
|
* except ban { mask *.irccloud.com; type { maxperip; connect-flood; } }.
|
|
* Unfortunately DNS sometimes fails to resolve. We have seen this happen
|
|
* during an outage or server restart. People then mass-connect, but DNS is
|
|
* not fully working (yet), leading to unresolved hostnames.
|
|
* In May 2026 we added stricter maxperip treatment for /64 IPv6, and in
|
|
* connthrottle we added /56, /48 and /32 restrictions. Without these IP
|
|
* exceptions this would cause unwanted rejections.
|
|
*/
|
|
static void add_known_cloud_services_exempts(void)
|
|
{
|
|
int i;
|
|
|
|
if (!known_cloud_services)
|
|
return;
|
|
|
|
for (i = 0; irccloud_cidrs[i]; i++)
|
|
{
|
|
tkl_add_banexception(TKL_EXCEPTION, "*", irccloud_cidrs[i], NULL,
|
|
"IRCCloud default maxperip/connect-flood exemption", "-default-",
|
|
0, TStime(), 0, "mc", TKL_FLAG_CONFIG);
|
|
}
|
|
}
|
|
|
|
int stats_maxperip(Client *client, const char *para)
|
|
{
|
|
int i;
|
|
IpUsersBucket *e;
|
|
char ipbuf[256];
|
|
const char *ip;
|
|
|
|
/* '/STATS 8' or '/STATS maxperip' is for us... */
|
|
if (strcmp(para, "8") && strcasecmp(para, "maxperip"))
|
|
return 0;
|
|
|
|
if (!ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL))
|
|
{
|
|
sendnumeric(client, ERR_NOPRIVILEGES);
|
|
return 0;
|
|
}
|
|
|
|
sendtxtnumeric(client, "MaxPerIp IPv4 hash table:");
|
|
for (i=0; i < IPUSERS_HASH_TABLE_SIZE; i++)
|
|
{
|
|
for (e = IpUsersHash_ipv4[i]; e; e = e->next)
|
|
{
|
|
ip = inetntop(AF_INET, e->rawip, ipbuf, sizeof(ipbuf));
|
|
if (!ip)
|
|
ip = "<invalid>";
|
|
sendtxtnumeric(client, "IPv4 #%d %s: %d local / %d global",
|
|
i, ip, e->local_clients, e->global_clients);
|
|
}
|
|
}
|
|
|
|
sendtxtnumeric(client, "MaxPerIp IPv6 hash table:");
|
|
for (i=0; i < IPUSERS_HASH_TABLE_SIZE; i++)
|
|
{
|
|
for (e = IpUsersHash_ipv6[i]; e; e = e->next)
|
|
{
|
|
ip = inet_ntop(AF_INET6, e->rawip, ipbuf, sizeof(ipbuf));
|
|
if (!ip)
|
|
ip = "<invalid>";
|
|
sendtxtnumeric(client, "IPv6 #%d %s/%d: %d local / %d global",
|
|
i, ip, iConf.default_ipv6_clone_mask,
|
|
e->local_clients, e->global_clients);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/** Returns 1 if allow::maxperip is exceeded by 'client' */
|
|
int exceeds_maxperip(Client *client, ConfigItem_allow *aconf)
|
|
{
|
|
Client *acptr;
|
|
IpUsersBucket *bucket;
|
|
|
|
if (!client->ip)
|
|
return 0; /* eg. services */
|
|
|
|
bucket = find_ipusers_bucket(client);
|
|
if (!bucket)
|
|
{
|
|
client->flags |= CLIENT_FLAG_IPUSERS_BUMPED;
|
|
bucket = add_ipusers_bucket(client);
|
|
bucket->global_clients = 1;
|
|
if (MyConnect(client))
|
|
bucket->local_clients = 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Bump if we haven't done so yet
|
|
* (Actually not sure if this can ever be false, but...
|
|
* who knows with some 3rd party or some future change)
|
|
*/
|
|
if (!(client->flags & CLIENT_FLAG_IPUSERS_BUMPED))
|
|
{
|
|
bucket->global_clients++;
|
|
if (MyConnect(client))
|
|
bucket->local_clients++;
|
|
client->flags |= CLIENT_FLAG_IPUSERS_BUMPED;
|
|
}
|
|
|
|
if (find_tkl_exception(TKL_MAXPERIP, client))
|
|
return 0; /* exempt */
|
|
|
|
if (aconf)
|
|
{
|
|
if ((bucket->local_clients > aconf->maxperip) ||
|
|
(bucket->global_clients > aconf->global_maxperip))
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Called for remote connects, to track global max restrictions */
|
|
int maxperip_remote_connect(Client *client)
|
|
{
|
|
exceeds_maxperip(client, NULL);
|
|
return 0;
|
|
}
|
|
|
|
/** Called from AllowClient(), to deal with restrictions */
|
|
const char *maxperip_allow_client(Client *client, ConfigItem_allow *aconf)
|
|
{
|
|
if (exceeds_maxperip(client, aconf))
|
|
{
|
|
IpUsersBucket *bucket = find_ipusers_bucket(client);
|
|
|
|
if (IsIPV6(client) && iConf.default_ipv6_clone_mask < 128)
|
|
{
|
|
char masked[16];
|
|
mask_ipv6_rawip(client->rawip, iConf.default_ipv6_clone_mask, masked);
|
|
if (bucket && bucket->local_clients > aconf->maxperip)
|
|
unreal_log(ULOG_INFO, "maxperip", "MAXPERIP_LIMIT", client,
|
|
"Client $client.name with IP $client.ip rejected: maxperip limit exceeded for $prefix_addr/$prefix_len ($count local, max $max)",
|
|
log_data_string("prefix_addr", format_ipv6_addr(masked)),
|
|
log_data_integer("prefix_len", iConf.default_ipv6_clone_mask),
|
|
log_data_integer("count", bucket->local_clients),
|
|
log_data_integer("max", aconf->maxperip));
|
|
else
|
|
unreal_log(ULOG_INFO, "maxperip", "MAXPERIP_LIMIT", client,
|
|
"Client $client.name with IP $client.ip rejected: maxperip limit exceeded for $prefix_addr/$prefix_len ($count global, max $max)",
|
|
log_data_string("prefix_addr", format_ipv6_addr(masked)),
|
|
log_data_integer("prefix_len", iConf.default_ipv6_clone_mask),
|
|
log_data_integer("count", bucket ? bucket->global_clients : 0),
|
|
log_data_integer("max", aconf->global_maxperip));
|
|
return format_ipv6_prefix_reject_message(
|
|
iConf.reject_message_too_many_connections_ipv6_range,
|
|
masked, iConf.default_ipv6_clone_mask);
|
|
}
|
|
if (bucket && bucket->local_clients > aconf->maxperip)
|
|
unreal_log(ULOG_INFO, "maxperip", "MAXPERIP_LIMIT", client,
|
|
"Client $client.name with IP $client.ip rejected: maxperip limit exceeded ($count local, max $max)",
|
|
log_data_integer("count", bucket->local_clients),
|
|
log_data_integer("max", aconf->maxperip));
|
|
else
|
|
unreal_log(ULOG_INFO, "maxperip", "MAXPERIP_LIMIT", client,
|
|
"Client $client.name with IP $client.ip rejected: maxperip limit exceeded ($count global, max $max)",
|
|
log_data_integer("count", bucket ? bucket->global_clients : 0),
|
|
log_data_integer("max", aconf->global_maxperip));
|
|
return iConf.reject_message_too_many_connections;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/** Return the number of connections from the same IP as 'client' */
|
|
int _get_connections_from_ip(Client *client)
|
|
{
|
|
IpUsersBucket *bucket;
|
|
|
|
if (!client->ip)
|
|
return 0;
|
|
|
|
bucket = find_ipusers_bucket(client);
|
|
if (!bucket)
|
|
return 0;
|
|
|
|
return bucket->global_clients;
|
|
}
|